Merge remote-tracking branch 'requarks/latest' into tyclipso

pull/7734/head
Carl Richter 4 years ago
commit c9046cdef9

@ -13,7 +13,7 @@ We welcome any type of contribution, not only code. You can help with
## Your First Contribution ## Your First Contribution
Working on your first Pull Request? You can learn how from this *free* series, [How to Contribute to an Open Source Project on GitHub](https://egghead.io/series/how-to-contribute-to-an-open-source-project-on-github). Working on your first Pull Request? You can learn how from this *free* course, [How to Contribute to an Open Source Project on GitHub](https://egghead.io/courses/how-to-contribute-to-an-open-source-project-on-github).
## Submitting code ## Submitting code
@ -39,8 +39,8 @@ Anyone can file an expense. If the expense makes sense for the development of th
## Questions ## Questions
If you have any questions, create an [issue](issue) (protip: do a quick search first to see if someone else didn't ask the same question before!). If you have any questions, create an [issue](https://github.com/Requarks/wiki/issues/new/choose) (protip: do a quick search first to see if someone else didn't ask the same question before!).
You can also reach us at hello@wikijs.opencollective.com. You can also reach us at <hello@wikijs.opencollective.com>.
## Credits ## Credits

@ -0,0 +1,26 @@
name: Helm Chart CI
on:
# Triggers the workflow on push or pull request events but only for the dev branch
push:
branches: [ dev ]
paths: [ dev/helm/** ]
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
jobs:
build:
name: Update Chart
runs-on: ubuntu-latest
steps:
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
- uses: actions/checkout@v2
- name: Package and Push Chart
run: |
helm plugin install https://github.com/chartmuseum/helm-push.git
helm repo add chartmuseum https://charts.js.wiki
helm cm-push --version="2.2.${{github.run_number}}" --username="${{secrets.HELM_REPO_USERNAME}}" --password="${{secrets.HELM_REPO_PASSWORD}}" dev/helm/ chartmuseum
helm repo remove chartmuseum

@ -9,5 +9,8 @@
], ],
"editor.codeActionsOnSave": { "editor.codeActionsOnSave": {
"source.fixAll.eslint": true "source.fixAll.eslint": true
} },
"i18n-ally.localesPaths": [
"server/locales"
]
} }

@ -5,13 +5,14 @@
[![Release](https://img.shields.io/github/release/Requarks/wiki.svg?style=flat&maxAge=3600)](https://github.com/Requarks/wiki/releases) [![Release](https://img.shields.io/github/release/Requarks/wiki.svg?style=flat&maxAge=3600)](https://github.com/Requarks/wiki/releases)
[![License](https://img.shields.io/badge/license-AGPLv3-blue.svg?style=flat)](https://github.com/requarks/wiki/blob/master/LICENSE) [![License](https://img.shields.io/badge/license-AGPLv3-blue.svg?style=flat)](https://github.com/requarks/wiki/blob/master/LICENSE)
[![Backers on Open Collective](https://opencollective.com/wikijs/all/badge.svg)](https://opencollective.com/wikijs) [![Backers on Open Collective](https://opencollective.com/wikijs/all/badge.svg)](https://opencollective.com/wikijs)
[![Downloads](https://img.shields.io/github/downloads/Requarks/wiki/total.svg?style=flat)](https://github.com/Requarks/wiki/releases) [![Downloads](https://img.shields.io/github/downloads/Requarks/wiki/total.svg?style=flat&logo=github)](https://github.com/Requarks/wiki/releases)
[![Docker Pulls](https://img.shields.io/docker/pulls/requarks/wiki.svg)](https://hub.docker.com/r/requarks/wiki/) [![Docker Pulls](https://img.shields.io/docker/pulls/requarks/wiki.svg?logo=docker&logoColor=white)](https://hub.docker.com/r/requarks/wiki/)
[![Build status](https://dev.azure.com/requarks/wiki/_apis/build/status/build)](https://dev.azure.com/requarks/wiki/_build/latest?definitionId=9) [![Build status](https://dev.azure.com/requarks/wiki/_apis/build/status/build)](https://dev.azure.com/requarks/wiki/_build/latest?definitionId=9)
[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=wiki&metric=alert_status)](https://sonarcloud.io/dashboard?id=wiki) [![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=wiki&metric=alert_status)](https://sonarcloud.io/dashboard?id=wiki)
[![Maintainability Rating](https://sonarcloud.io/api/project_badges/measure?project=wiki&metric=sqale_rating)](https://sonarcloud.io/dashboard?id=wiki) [![Maintainability Rating](https://sonarcloud.io/api/project_badges/measure?project=wiki&metric=sqale_rating)](https://sonarcloud.io/dashboard?id=wiki)
[![Security Rating](https://sonarcloud.io/api/project_badges/measure?project=wiki&metric=security_rating)](https://sonarcloud.io/dashboard?id=wiki) [![Security Rating](https://sonarcloud.io/api/project_badges/measure?project=wiki&metric=security_rating)](https://sonarcloud.io/dashboard?id=wiki)
[![Standard - JavaScript Style Guide](https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat)](http://standardjs.com/) [![Standard - JavaScript Style Guide](https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat)](http://standardjs.com/)
[![Huntr](https://img.shields.io/badge/security%20bounty-disclose-brightgreen.svg?style=flat&logo=cachet)](https://huntr.dev/bounties/disclose)
[![Chat on Slack](https://img.shields.io/badge/slack-requarks-CC2B5E.svg?style=flat&logo=slack)](https://wiki.requarks.io/slack) [![Chat on Slack](https://img.shields.io/badge/slack-requarks-CC2B5E.svg?style=flat&logo=slack)](https://wiki.requarks.io/slack)
[![Twitter Follow](https://img.shields.io/badge/follow-%40requarks-blue.svg?style=flat&logo=twitter)](https://twitter.com/requarks) [![Twitter Follow](https://img.shields.io/badge/follow-%40requarks-blue.svg?style=flat&logo=twitter)](https://twitter.com/requarks)
[![Reddit](https://img.shields.io/badge/reddit-%2Fr%2Fwikijs-orange?logo=reddit&logoColor=white)](https://www.reddit.com/r/wikijs/) [![Reddit](https://img.shields.io/badge/reddit-%2Fr%2Fwikijs-orange?logo=reddit&logoColor=white)](https://www.reddit.com/r/wikijs/)
@ -52,10 +53,45 @@ Wiki.js is an open source project that has been made possible due to the generou
</div> </div>
<h2 align="center">Gold Tier Sponsors</h2>
<div align="center">
<table>
<tbody>
<tr>
<td align="center" valign="middle" width="444">
<a href="https://trans-zero.com/" target="_blank">
<img src="https://cdn.js.wiki/images/sponsors/transzero.png">
</a>
</td>
</tr>
</tbody>
</table>
</div>
<h2 align="center">GitHub Sponsors</h2> <h2 align="center">GitHub Sponsors</h2>
Support this project by becoming a sponsor. Your name will show up in the Contribute page of all Wiki.js installations as well as here with a link to your website! [[Become a sponsor](https://github.com/users/NGPixel/sponsorship)] Support this project by becoming a sponsor. Your name will show up in the Contribute page of all Wiki.js installations as well as here with a link to your website! [[Become a sponsor](https://github.com/users/NGPixel/sponsorship)]
<div align="center">
<table>
<tbody>
<tr>
<td align="center" valign="middle" width="444">
<a href="https://www.stellarhosted.com/" target="_blank">
<img src="https://cdn.js.wiki/images/sponsors/stellarhosted.png">
</a>
</td>
<td align="center" valign="middle" width="444">
<a href="https://www.hostwiki.com/" target="_blank">
<img src="https://cdn.js.wiki/images/sponsors/hostwiki.png">
</a>
</td>
</tr>
</tbody>
</table>
</div>
<div align="center"> <div align="center">
<table> <table>
<tbody> <tbody>
@ -65,6 +101,11 @@ Support this project by becoming a sponsor. Your name will show up in the Contri
Alexander Casassovici<br />(@alexksso) Alexander Casassovici<br />(@alexksso)
</a> </a>
</td> </td>
<td align="center" valign="middle" width="148">
<a href="https://github.com/broxen" target="_blank">
Broxen<br />(@broxen)
</a>
</td>
<td align="center" valign="middle" width="148"> <td align="center" valign="middle" width="148">
<a href="https://github.com/xDacon" target="_blank"> <a href="https://github.com/xDacon" target="_blank">
Dacon<br />(@xDacon) Dacon<br />(@xDacon)
@ -85,11 +126,6 @@ Support this project by becoming a sponsor. Your name will show up in the Contri
Oleksii<br />(@idokka) Oleksii<br />(@idokka)
</a> </a>
</td> </td>
<td align="center" valign="middle" width="148">
<a href="https://github.com/TheodoreChu" target="_blank">
Theodore Chu<br />(@TheodoreChu)
</a>
</td>
<!--<td align="center" valign="middle" width="148"> <!--<td align="center" valign="middle" width="148">
<a href="https://github.com/sponsors/NGPixel" target="_blank"> <a href="https://github.com/sponsors/NGPixel" target="_blank">
<img src="https://static.requarks.io/sponsors/become-148x72.png"> <img src="https://static.requarks.io/sponsors/become-148x72.png">
@ -102,6 +138,7 @@ Support this project by becoming a sponsor. Your name will show up in the Contri
- Akira Suenami ([@a-suenami](https://github.com/a-suenami)) - Akira Suenami ([@a-suenami](https://github.com/a-suenami))
- Arnaud Marchand ([@snuids](https://github.com/snuids)) - Arnaud Marchand ([@snuids](https://github.com/snuids))
- Brian Douglass ([@bhdouglass](https://github.com/bhdouglass))
- Bryon Vandiver ([@asterick](https://github.com/asterick)) - Bryon Vandiver ([@asterick](https://github.com/asterick))
- Cameron Steele ([@ATechAdventurer](https://github.com/ATechAdventurer)) - Cameron Steele ([@ATechAdventurer](https://github.com/ATechAdventurer))
- Cloud Data Hosting LLC ([@CloudDataHostingLLC](https://github.com/CloudDataHostingLLC)) - Cloud Data Hosting LLC ([@CloudDataHostingLLC](https://github.com/CloudDataHostingLLC))
@ -110,12 +147,14 @@ Support this project by becoming a sponsor. Your name will show up in the Contri
- Dragan Espenschied ([@despens](https://github.com/despens)) - Dragan Espenschied ([@despens](https://github.com/despens))
- Elijah Zobenko ([@he110](https://github.com/he110)) - Elijah Zobenko ([@he110](https://github.com/he110))
- Ernie ([@iamernie](https://github.com/iamernie)) - Ernie ([@iamernie](https://github.com/iamernie))
- Fabio Ferrari ([@devxops](https://github.com/devxops))
- Florian Moss ([@florianmoss](https://github.com/florianmoss)) - Florian Moss ([@florianmoss](https://github.com/florianmoss))
- HeavenBay ([@HeavenBay](https://github.com/heavenbay)) - HeavenBay ([@HeavenBay](https://github.com/heavenbay))
- Jaimyn Mayer ([@jabelone](https://github.com/jabelone)) - Jaimyn Mayer ([@jabelone](https://github.com/jabelone))
- Jay Lee ([@polyglotm](https://github.com/polyglotm)) - Jay Lee ([@polyglotm](https://github.com/polyglotm))
- Kelly Wardrop ([@dropcoded](https://github.com/dropcoded)) - Kelly Wardrop ([@dropcoded](https://github.com/dropcoded))
- Loki ([@binaryloki](https://github.com/binaryloki)) - Loki ([@binaryloki](https://github.com/binaryloki))
- MaFarine ([@MaFarine](https://github.com/MaFarine))
- Marcilio Leite Neto ([@marclneto](https://github.com/marclneto)) - Marcilio Leite Neto ([@marclneto](https://github.com/marclneto))
- Mattias Johnson ([@mattiasJohnson](https://github.com/mattiasJohnson)) - Mattias Johnson ([@mattiasJohnson](https://github.com/mattiasJohnson))
- Mitchell Rowton ([@mrowton](https://github.com/mrowton)) - Mitchell Rowton ([@mrowton](https://github.com/mrowton))
@ -127,12 +166,16 @@ Support this project by becoming a sponsor. Your name will show up in the Contri
- Robert Lanzke ([@winkelement](https://github.com/winkelement)) - Robert Lanzke ([@winkelement](https://github.com/winkelement))
- Sam Martin ([@winkelement](https://github.com/ABitMoreDepth)) - Sam Martin ([@winkelement](https://github.com/ABitMoreDepth))
- Sean Coffey ([@seanecoffey](https://github.com/seanecoffey)) - Sean Coffey ([@seanecoffey](https://github.com/seanecoffey))
- Stephan Kristyn ([@stevek-pro](https://github.com/stevek-pro))
- Theodore Chu ([@TheodoreChu](https://github.com/TheodoreChu))
- Tyler Denman ([@tylerguy](https://github.com/tylerguy))
- Victor Bilgin ([@vbilgin](https://github.com/vbilgin)) - Victor Bilgin ([@vbilgin](https://github.com/vbilgin))
- VMO Solutions ([@vmosolutions](https://github.com/vmosolutions)) - VMO Solutions ([@vmosolutions](https://github.com/vmosolutions))
- aniketpanjwani ([@aniketpanjwani](https://github.com/aniketpanjwani)) - aniketpanjwani ([@aniketpanjwani](https://github.com/aniketpanjwani))
- aytaa ([@aytaa](https://github.com/aytaa)) - aytaa ([@aytaa](https://github.com/aytaa))
- magicpotato ([@fortheday](https://github.com/fortheday)) - magicpotato ([@fortheday](https://github.com/fortheday))
- motoacs ([@motoacs](https://github.com/motoacs)) - motoacs ([@motoacs](https://github.com/motoacs))
- rburckner ([@rburckner](https://github.com/rburckner))
- scorpion ([@scorpion](https://github.com/scorpion)) - scorpion ([@scorpion](https://github.com/scorpion))
- valantien ([@valantien](https://github.com/valantien)) - valantien ([@valantien](https://github.com/valantien))
@ -144,88 +187,123 @@ Support this project by becoming a sponsor. Your logo will show up in the Contri
<table> <table>
<tbody> <tbody>
<tr> <tr>
<td align="center" valign="middle" width="192"> <td align="center" valign="middle">
<a href="https://opencollective.com/wikijs/sponsor/0/website" target="_blank"><img src="https://opencollective.com/wikijs/sponsor/0/avatar.svg"></a> <a href="https://opencollective.com/wikijs/sponsor/0/website" target="_blank"><img src="https://opencollective.com/wikijs/sponsor/0/avatar.svg"></a>
</td> </td>
<td align="center" valign="middle" width="192"> <td align="center" valign="middle">
<a href="https://opencollective.com/wikijs/sponsor/1/website" target="_blank"><img src="https://opencollective.com/wikijs/sponsor/1/avatar.svg"></a> <a href="https://opencollective.com/wikijs/sponsor/1/website" target="_blank"><img src="https://opencollective.com/wikijs/sponsor/1/avatar.svg"></a>
</td> </td>
<td align="center" valign="middle" width="192"> <td align="center" valign="middle">
<a href="https://opencollective.com/wikijs/sponsor/2/website" target="_blank"><img src="https://opencollective.com/wikijs/sponsor/2/avatar.svg"></a> <a href="https://opencollective.com/wikijs/sponsor/2/website" target="_blank"><img src="https://opencollective.com/wikijs/sponsor/2/avatar.svg"></a>
</td> </td>
<td align="center" valign="middle" width="192"> <td align="center" valign="middle">
<a href="https://opencollective.com/wikijs/sponsor/3/website" target="_blank"><img src="https://opencollective.com/wikijs/sponsor/3/avatar.svg"></a> <a href="https://opencollective.com/wikijs/sponsor/3/website" target="_blank"><img src="https://opencollective.com/wikijs/sponsor/3/avatar.svg"></a>
</td> </td>
</tr> <td align="center" valign="middle">
<tr>
<td align="center" valign="middle" width="192">
<a href="https://opencollective.com/wikijs/sponsor/4/website" target="_blank"><img src="https://opencollective.com/wikijs/sponsor/4/avatar.svg"></a> <a href="https://opencollective.com/wikijs/sponsor/4/website" target="_blank"><img src="https://opencollective.com/wikijs/sponsor/4/avatar.svg"></a>
</td> </td>
<td align="center" valign="middle" width="192"> </tr>
<tr>
<td align="center" valign="middle">
<a href="https://opencollective.com/wikijs/sponsor/5/website" target="_blank"><img src="https://opencollective.com/wikijs/sponsor/5/avatar.svg"></a> <a href="https://opencollective.com/wikijs/sponsor/5/website" target="_blank"><img src="https://opencollective.com/wikijs/sponsor/5/avatar.svg"></a>
</td> </td>
<td align="center" valign="middle" width="192"> <td align="center" valign="middle">
<a href="https://opencollective.com/wikijs/sponsor/6/website" target="_blank"><img src="https://opencollective.com/wikijs/sponsor/6/avatar.svg"></a> <a href="https://opencollective.com/wikijs/sponsor/6/website" target="_blank"><img src="https://opencollective.com/wikijs/sponsor/6/avatar.svg"></a>
</td> </td>
<td align="center" valign="middle" width="192"> <td align="center" valign="middle">
<a href="https://opencollective.com/wikijs/sponsor/7/website" target="_blank"><img src="https://opencollective.com/wikijs/sponsor/7/avatar.svg"></a> <a href="https://opencollective.com/wikijs/sponsor/7/website" target="_blank"><img src="https://opencollective.com/wikijs/sponsor/7/avatar.svg"></a>
</td> </td>
</tr> <td align="center" valign="middle">
<tr>
<td align="center" valign="middle" width="192">
<a href="https://opencollective.com/wikijs/sponsor/8/website" target="_blank"><img src="https://opencollective.com/wikijs/sponsor/8/avatar.svg"></a> <a href="https://opencollective.com/wikijs/sponsor/8/website" target="_blank"><img src="https://opencollective.com/wikijs/sponsor/8/avatar.svg"></a>
</td> </td>
<td align="center" valign="middle" width="192"> <td align="center" valign="middle">
<a href="https://opencollective.com/wikijs/sponsor/9/website" target="_blank"><img src="https://opencollective.com/wikijs/sponsor/9/avatar.svg"></a> <a href="https://opencollective.com/wikijs/sponsor/9/website" target="_blank"><img src="https://opencollective.com/wikijs/sponsor/9/avatar.svg"></a>
</td> </td>
<td align="center" valign="middle" width="192"> </tr>
<tr>
<td align="center" valign="middle">
<a href="https://opencollective.com/wikijs/sponsor/10/website" target="_blank"><img src="https://opencollective.com/wikijs/sponsor/10/avatar.svg"></a> <a href="https://opencollective.com/wikijs/sponsor/10/website" target="_blank"><img src="https://opencollective.com/wikijs/sponsor/10/avatar.svg"></a>
</td> </td>
<td align="center" valign="middle" width="192"> <td align="center" valign="middle">
<a href="https://opencollective.com/wikijs/sponsor/11/website" target="_blank"><img src="https://opencollective.com/wikijs/sponsor/11/avatar.svg"></a> <a href="https://opencollective.com/wikijs/sponsor/11/website" target="_blank"><img src="https://opencollective.com/wikijs/sponsor/11/avatar.svg"></a>
</td> </td>
</tr> <td align="center" valign="middle">
<tr>
<td align="center" valign="middle" width="192">
<a href="https://opencollective.com/wikijs/sponsor/12/website" target="_blank"><img src="https://opencollective.com/wikijs/sponsor/12/avatar.svg"></a> <a href="https://opencollective.com/wikijs/sponsor/12/website" target="_blank"><img src="https://opencollective.com/wikijs/sponsor/12/avatar.svg"></a>
</td> </td>
<td align="center" valign="middle" width="192"> <td align="center" valign="middle">
<a href="https://opencollective.com/wikijs/sponsor/13/website" target="_blank"><img src="https://opencollective.com/wikijs/sponsor/13/avatar.svg"></a> <a href="https://opencollective.com/wikijs/sponsor/13/website" target="_blank"><img src="https://opencollective.com/wikijs/sponsor/13/avatar.svg"></a>
</td> </td>
<td align="center" valign="middle" width="192"> <td align="center" valign="middle">
<a href="https://opencollective.com/wikijs/sponsor/14/website" target="_blank"><img src="https://opencollective.com/wikijs/sponsor/14/avatar.svg"></a> <a href="https://opencollective.com/wikijs/sponsor/14/website" target="_blank"><img src="https://opencollective.com/wikijs/sponsor/14/avatar.svg"></a>
</td> </td>
<td align="center" valign="middle" width="192">
<a href="https://opencollective.com/wikijs/sponsor/15/website" target="_blank"><img src="https://opencollective.com/wikijs/sponsor/15/avatar.svg"></a>
</td>
</tr> </tr>
<tr> <tr>
<td align="center" valign="middle" width="192"> <td align="center" valign="middle">
<a href="https://opencollective.com/wikijs/sponsor/15/website" target="_blank"><img src="https://opencollective.com/wikijs/sponsor/15/avatar.svg"></a>
</td>
<td align="center" valign="middle">
<a href="https://opencollective.com/wikijs/sponsor/16/website" target="_blank"><img src="https://opencollective.com/wikijs/sponsor/16/avatar.svg"></a> <a href="https://opencollective.com/wikijs/sponsor/16/website" target="_blank"><img src="https://opencollective.com/wikijs/sponsor/16/avatar.svg"></a>
</td> </td>
<td align="center" valign="middle" width="192"> <td align="center" valign="middle">
<a href="https://opencollective.com/wikijs/sponsor/17/website" target="_blank"><img src="https://opencollective.com/wikijs/sponsor/17/avatar.svg"></a> <a href="https://opencollective.com/wikijs/sponsor/17/website" target="_blank"><img src="https://opencollective.com/wikijs/sponsor/17/avatar.svg"></a>
</td> </td>
<td align="center" valign="middle" width="192"> <td align="center" valign="middle">
<a href="https://opencollective.com/wikijs/sponsor/18/website" target="_blank"><img src="https://opencollective.com/wikijs/sponsor/18/avatar.svg"></a> <a href="https://opencollective.com/wikijs/sponsor/18/website" target="_blank"><img src="https://opencollective.com/wikijs/sponsor/18/avatar.svg"></a>
</td> </td>
<td align="center" valign="middle" width="192"> <td align="center" valign="middle">
<a href="https://opencollective.com/wikijs/sponsor/19/website" target="_blank"><img src="https://opencollective.com/wikijs/sponsor/19/avatar.svg"></a> <a href="https://opencollective.com/wikijs/sponsor/19/website" target="_blank"><img src="https://opencollective.com/wikijs/sponsor/19/avatar.svg"></a>
</td> </td>
</tr> </tr>
<tr> <tr>
<td align="center" valign="middle" width="192"> <td align="center" valign="middle">
<a href="https://opencollective.com/wikijs/sponsor/20/website" target="_blank"><img src="https://opencollective.com/wikijs/sponsor/20/avatar.svg"></a> <a href="https://opencollective.com/wikijs/sponsor/20/website" target="_blank"><img src="https://opencollective.com/wikijs/sponsor/20/avatar.svg"></a>
</td> </td>
<td align="center" valign="middle" width="192"> <td align="center" valign="middle">
<a href="https://opencollective.com/wikijs/sponsor/21/website" target="_blank"><img src="https://opencollective.com/wikijs/sponsor/21/avatar.svg"></a> <a href="https://opencollective.com/wikijs/sponsor/21/website" target="_blank"><img src="https://opencollective.com/wikijs/sponsor/21/avatar.svg"></a>
</td> </td>
<td align="center" valign="middle" width="192"> <td align="center" valign="middle">
<a href="https://opencollective.com/wikijs/sponsor/22/website" target="_blank"><img src="https://opencollective.com/wikijs/sponsor/22/avatar.svg"></a> <a href="https://opencollective.com/wikijs/sponsor/22/website" target="_blank"><img src="https://opencollective.com/wikijs/sponsor/22/avatar.svg"></a>
</td> </td>
<td align="center" valign="middle" width="192"> <td align="center" valign="middle">
<a href="https://opencollective.com/wikijs/sponsor/23/website" target="_blank"><img src="https://opencollective.com/wikijs/sponsor/23/avatar.svg"></a> <a href="https://opencollective.com/wikijs/sponsor/23/website" target="_blank"><img src="https://opencollective.com/wikijs/sponsor/23/avatar.svg"></a>
</td> </td>
<td align="center" valign="middle">
<a href="https://opencollective.com/wikijs/sponsor/24/website" target="_blank"><img src="https://opencollective.com/wikijs/sponsor/24/avatar.svg"></a>
</td>
</tr>
<tr>
<td align="center" valign="middle">
<a href="https://opencollective.com/wikijs/sponsor/25/website" target="_blank"><img src="https://opencollective.com/wikijs/sponsor/25/avatar.svg"></a>
</td>
<td align="center" valign="middle">
<a href="https://opencollective.com/wikijs/sponsor/26/website" target="_blank"><img src="https://opencollective.com/wikijs/sponsor/26/avatar.svg"></a>
</td>
<td align="center" valign="middle">
<a href="https://opencollective.com/wikijs/sponsor/27/website" target="_blank"><img src="https://opencollective.com/wikijs/sponsor/27/avatar.svg"></a>
</td>
<td align="center" valign="middle">
<a href="https://opencollective.com/wikijs/sponsor/28/website" target="_blank"><img src="https://opencollective.com/wikijs/sponsor/28/avatar.svg"></a>
</td>
<td align="center" valign="middle">
<a href="https://opencollective.com/wikijs/sponsor/29/website" target="_blank"><img src="https://opencollective.com/wikijs/sponsor/29/avatar.svg"></a>
</td>
</tr>
<tr>
<td align="center" valign="middle">
<a href="https://opencollective.com/wikijs/sponsor/30/website" target="_blank"><img src="https://opencollective.com/wikijs/sponsor/30/avatar.svg"></a>
</td>
<td align="center" valign="middle">
<a href="https://opencollective.com/wikijs/sponsor/31/website" target="_blank"><img src="https://opencollective.com/wikijs/sponsor/31/avatar.svg"></a>
</td>
<td align="center" valign="middle">
<a href="https://opencollective.com/wikijs/sponsor/32/website" target="_blank"><img src="https://opencollective.com/wikijs/sponsor/32/avatar.svg"></a>
</td>
<td align="center" valign="middle">
<a href="https://opencollective.com/wikijs/sponsor/33/website" target="_blank"><img src="https://opencollective.com/wikijs/sponsor/33/avatar.svg"></a>
</td>
<td align="center" valign="middle">
<a href="https://opencollective.com/wikijs/sponsor/34/website" target="_blank"><img src="https://opencollective.com/wikijs/sponsor/34/avatar.svg"></a>
</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
@ -283,16 +361,16 @@ This project exists thanks to all the people who contribute. [[Contribute]](http
<h2 align="center">Special Thanks</h2> <h2 align="center">Special Thanks</h2>
![Algolia](https://wiki.js.org/legacy/logo_algolia.png) ![Algolia](https://js.wiki/legacy/logo_algolia.png)
[Algolia](https://www.algolia.com/) for providing access to their incredible search engine. [Algolia](https://www.algolia.com/) for providing access to their incredible search engine.
![Browserstack](https://wiki.js.org/legacy/logo_browserstack.png) ![Browserstack](https://js.wiki/legacy/logo_browserstack.png)
[Browserstack](https://www.browserstack.com/) for providing access to their great cross-browser testing tools. [Browserstack](https://www.browserstack.com/) for providing access to their great cross-browser testing tools.
![Cloudflare](https://wiki.js.org/legacy/logo_cloudflare.png) ![Cloudflare](https://js.wiki/legacy/logo_cloudflare.png)
[Cloudflare](https://www.cloudflare.com/) for providing their great CDN, SSL and advanced networking services. [Cloudflare](https://www.cloudflare.com/) for providing their great CDN, SSL and advanced networking services.
![DigitalOcean](https://wiki.js.org/legacy/logo_digitalocean.png) ![DigitalOcean](https://js.wiki/legacy/logo_digitalocean.png)
[DigitalOcean](https://m.do.co/c/5f7445bfa4d0) for providing hosting of the Wiki.js documentation site. [DigitalOcean](https://m.do.co/c/5f7445bfa4d0) for providing hosting of the Wiki.js documentation site.
![Icons8](https://static.requarks.io/logo/icons8-text-h40.png) ![Icons8](https://static.requarks.io/logo/icons8-text-h40.png)
@ -301,8 +379,11 @@ This project exists thanks to all the people who contribute. [[Contribute]](http
![Lokalise](https://static.requarks.io/logo/lokalise-text-h40.png) ![Lokalise](https://static.requarks.io/logo/lokalise-text-h40.png)
[Lokalise](https://lokalise.com/) for providing access to their great localization tool. [Lokalise](https://lokalise.com/) for providing access to their great localization tool.
![Netlify](https://wiki.js.org/legacy/logo_netlify.png) ![Netlify](https://js.wiki/legacy/logo_netlify.png)
[Netlify](https://www.netlify.com) for providing hosting for landings and blog websites. [Netlify](https://www.netlify.com) for providing hosting for landings and blog websites.
![ngrok](https://static.requarks.io/logo/ngrok-h40.png)
[ngrok](https://ngrok.com) for providing access to their great HTTP tunneling services.
![Porkbun](https://static.requarks.io/logo/porkbun.png) ![Porkbun](https://static.requarks.io/logo/porkbun.png)
[Porkbun](https://www.porkbun.com) for providing domain registration services. [Porkbun](https://www.porkbun.com) for providing domain registration services.

@ -13,7 +13,15 @@ If you find such vulnerability, it's important to disclose it in a quick and sec
## Reporting a Vulnerability ## Reporting a Vulnerability
**DO NOT CREATE AN ISSUE ON GITHUB** to report a potential vulnerability / security problem. Instead, send an email to security@requarks.io. **DO NOT CREATE AN ISSUE ON GITHUB** to report a potential vulnerability / security problem. Instead, choose one of these options:
### A) Disclose on Huntr.dev
Disclose the vulnerability on [Huntr.dev](https://huntr.dev/bounties/disclose) for the repository `https://github.com/Requarks/wiki`.
### B) Send an email
Send an email to security@requarks.io.
Include as much details as possible, such as: Include as much details as possible, such as:
- The version(s) of Wiki.js that are impacted - The version(s) of Wiki.js that are impacted

@ -142,6 +142,24 @@
:suffix='$t(`admin:security.maxUploadBatchSuffix`)' :suffix='$t(`admin:security.maxUploadBatchSuffix`)'
style='max-width: 450px;' style='max-width: 450px;'
) )
v-divider.mt-3
v-switch(
inset
label='Scan and Sanitize SVG Uploads'
color='primary'
v-model='config.uploadScanSVG'
persistent-hint
hint='Should SVG uploads be scanned for vulnerabilities and stripped of any potentially unsafe content.'
)
v-divider.mt-3
v-switch(
inset
label='Force Download of Unsafe Extensions'
color='primary'
v-model='config.uploadForceDownload'
persistent-hint
hint='Should non-image files be forced as downloads when accessed directly. This prevents potential XSS attacks via unsafe file extensions uploads.'
)
v-card.mt-3.animated.fadeInUp.wait-p2s v-card.mt-3.animated.fadeInUp.wait-p2s
v-toolbar(flat, color='primary', dark, dense) v-toolbar(flat, color='primary', dark, dense)
@ -242,6 +260,8 @@ export default {
config: { config: {
uploadMaxFileSize: 0, uploadMaxFileSize: 0,
uploadMaxFiles: 0, uploadMaxFiles: 0,
uploadScanSVG: true,
uploadForceDownload: true,
securityOpenRedirect: true, securityOpenRedirect: true,
securityIframe: true, securityIframe: true,
securityReferrerPolicy: true, securityReferrerPolicy: true,
@ -286,6 +306,8 @@ export default {
$authJwtRenewablePeriod: String $authJwtRenewablePeriod: String
$uploadMaxFileSize: Int $uploadMaxFileSize: Int
$uploadMaxFiles: Int $uploadMaxFiles: Int
$uploadScanSVG: Boolean
$uploadForceDownload: Boolean
$securityOpenRedirect: Boolean $securityOpenRedirect: Boolean
$securityIframe: Boolean $securityIframe: Boolean
$securityReferrerPolicy: Boolean $securityReferrerPolicy: Boolean
@ -307,6 +329,8 @@ export default {
authJwtRenewablePeriod: $authJwtRenewablePeriod, authJwtRenewablePeriod: $authJwtRenewablePeriod,
uploadMaxFileSize: $uploadMaxFileSize, uploadMaxFileSize: $uploadMaxFileSize,
uploadMaxFiles: $uploadMaxFiles, uploadMaxFiles: $uploadMaxFiles,
uploadScanSVG: $uploadScanSVG
uploadForceDownload: $uploadForceDownload,
securityOpenRedirect: $securityOpenRedirect, securityOpenRedirect: $securityOpenRedirect,
securityIframe: $securityIframe, securityIframe: $securityIframe,
securityReferrerPolicy: $securityReferrerPolicy, securityReferrerPolicy: $securityReferrerPolicy,
@ -337,6 +361,8 @@ export default {
authJwtRenewablePeriod: _.get(this.config, 'authJwtRenewablePeriod', ''), authJwtRenewablePeriod: _.get(this.config, 'authJwtRenewablePeriod', ''),
uploadMaxFileSize: _.toSafeInteger(_.get(this.config, 'uploadMaxFileSize', 0)), uploadMaxFileSize: _.toSafeInteger(_.get(this.config, 'uploadMaxFileSize', 0)),
uploadMaxFiles: _.toSafeInteger(_.get(this.config, 'uploadMaxFiles', 0)), uploadMaxFiles: _.toSafeInteger(_.get(this.config, 'uploadMaxFiles', 0)),
uploadScanSVG: _.get(this.config, 'uploadScanSVG', false),
uploadForceDownload: _.get(this.config, 'uploadForceDownload', false),
securityOpenRedirect: _.get(this.config, 'securityOpenRedirect', false), securityOpenRedirect: _.get(this.config, 'securityOpenRedirect', false),
securityIframe: _.get(this.config, 'securityIframe', false), securityIframe: _.get(this.config, 'securityIframe', false),
securityReferrerPolicy: _.get(this.config, 'securityReferrerPolicy', false), securityReferrerPolicy: _.get(this.config, 'securityReferrerPolicy', false),
@ -388,6 +414,8 @@ export default {
authJwtRenewablePeriod authJwtRenewablePeriod
uploadMaxFileSize uploadMaxFileSize
uploadMaxFiles uploadMaxFiles
uploadScanSVG
uploadForceDownload
securityOpenRedirect securityOpenRedirect
securityIframe securityIframe
securityReferrerPolicy securityReferrerPolicy

@ -7,10 +7,10 @@
v-icon(color='grey', small) mdi-email-outline v-icon(color='grey', small) mdi-email-outline
v-list-item-title.px-3 Email v-list-item-title.px-3 Email
v-list-item(@click='openSocialPop(`https://www.facebook.com/sharer/sharer.php?u=` + encodeURIComponent(url) + `&title=` + encodeURIComponent(title) + `&description=` + encodeURIComponent(description))') v-list-item(@click='openSocialPop(`https://www.facebook.com/sharer/sharer.php?u=` + encodeURIComponent(url) + `&title=` + encodeURIComponent(title) + `&description=` + encodeURIComponent(description))')
v-icon(color='grey', small) mdi-facebook-box v-icon(color='grey', small) mdi-facebook
v-list-item-title.px-3 Facebook v-list-item-title.px-3 Facebook
v-list-item(@click='openSocialPop(`https://www.linkedin.com/shareArticle?mini=true&url=` + encodeURIComponent(url) + `&title=` + encodeURIComponent(title) + `&summary=` + encodeURIComponent(description))') v-list-item(@click='openSocialPop(`https://www.linkedin.com/shareArticle?mini=true&url=` + encodeURIComponent(url) + `&title=` + encodeURIComponent(title) + `&summary=` + encodeURIComponent(description))')
v-icon(color='grey', small) mdi-linkedin-box v-icon(color='grey', small) mdi-linkedin
v-list-item-title.px-3 LinkedIn v-list-item-title.px-3 LinkedIn
v-list-item(@click='openSocialPop(`https://www.reddit.com/submit?url=` + encodeURIComponent(url) + `&title=` + encodeURIComponent(title))') v-list-item(@click='openSocialPop(`https://www.reddit.com/submit?url=` + encodeURIComponent(url) + `&title=` + encodeURIComponent(title))')
v-icon(color='grey', small) mdi-reddit v-icon(color='grey', small) mdi-reddit

@ -290,7 +290,7 @@ const md = new MarkdownIt({
}) })
.use(underline) .use(underline)
.use(mdEmoji) .use(mdEmoji)
.use(mdTaskLists, {label: true, labelAfter: true}) .use(mdTaskLists, {label: false, labelAfter: false})
.use(mdExpandTabs) .use(mdExpandTabs)
.use(mdAbbr) .use(mdAbbr)
.use(mdSup) .use(mdSup)
@ -627,7 +627,7 @@ export default {
hint: async (cm, options) => { hint: async (cm, options) => {
const cur = cm.getCursor() const cur = cm.getCursor()
const curLine = cm.getLine(cur.line).substring(0, cur.ch) const curLine = cm.getLine(cur.line).substring(0, cur.ch)
const queryString = curLine.substring(curLine.lastIndexOf('[')+1,curLine.length-2) const queryString = curLine.substring(curLine.lastIndexOf('[') + 1, curLine.length - 2)
const token = cm.getTokenAt(cur) const token = cm.getTokenAt(cur)
try { try {
const respRaw = await this.$apollo.query({ const respRaw = await this.$apollo.query({

@ -143,7 +143,7 @@
allow-multiple='true' allow-multiple='true'
:files='files' :files='files'
max-files='10' max-files='10'
server='/u' :server='filePondServerOpts'
:instant-upload='false' :instant-upload='false'
:allow-revert='false' :allow-revert='false'
@processfile='onFileProcessed' @processfile='onFileProcessed'
@ -230,6 +230,7 @@
<script> <script>
import _ from 'lodash' import _ from 'lodash'
import { get, sync } from 'vuex-pathify' import { get, sync } from 'vuex-pathify'
import Cookies from 'js-cookie'
import vueFilePond from 'vue-filepond' import vueFilePond from 'vue-filepond'
import 'filepond/dist/filepond.min.css' import 'filepond/dist/filepond.min.css'
@ -312,6 +313,17 @@ export default {
}, },
currentAsset () { currentAsset () {
return _.find(this.assets, ['id', this.currentFileId]) || {} return _.find(this.assets, ['id', this.currentFileId]) || {}
},
filePondServerOpts () {
const jwtToken = Cookies.get('jwt')
return {
process: {
url: '/u',
headers: {
'Authorization': `Bearer ${jwtToken}`
}
}
}
} }
}, },
watch: { watch: {

@ -64,6 +64,7 @@
:hint='$t(`editor:props.pathHint`)' :hint='$t(`editor:props.pathHint`)'
persistent-hint persistent-hint
@click:append='showPathSelector' @click:append='showPathSelector'
:rules='[rules.required, rules.path]'
) )
v-divider v-divider
v-card-text.grey.pt-5(:class='$vuetify.theme.dark ? `darken-3-d5` : `lighten-4`') v-card-text.grey.pt-5(:class='$vuetify.theme.dark ? `darken-3-d5` : `lighten-4`')
@ -254,6 +255,7 @@ import 'codemirror/mode/htmlmixed/htmlmixed.js'
import 'codemirror/mode/css/css.js' import 'codemirror/mode/css/css.js'
/* global siteLangs, siteConfig */ /* global siteLangs, siteConfig */
const filenamePattern = /^(?![\#\/\.\$\^\=\*\;\:\&\?\(\)\[\]\{\}\"\'\>\<\,\@\!\%\`\~\s])(?!.*[\#\/\.\$\^\=\*\;\:\&\?\(\)\[\]\{\}\"\'\>\<\,\@\!\%\`\~\s]$)[^\#\.\$\^\=\*\;\:\&\?\(\)\[\]\{\}\"\'\>\<\,\@\!\%\`\~\s]*$/
export default { export default {
props: { props: {
@ -272,7 +274,13 @@ export default {
newTagSuggestions: [], newTagSuggestions: [],
newTagSearch: '', newTagSearch: '',
currentTab: 0, currentTab: 0,
cm: null cm: null,
rules: {
required: value => !!value || 'This field is required.',
path: value => {
return filenamePattern.test(value) || 'Invalid path. Please ensure it does not contain special characters, or begin/end in a slash or hashtag string.'
}
}
} }
}, },
computed: { computed: {

@ -67,7 +67,7 @@ export default {
this.$store.commit('page/SET_MODE', 'source') this.$store.commit('page/SET_MODE', 'source')
if (this.effectivePermissions) { if (this.effectivePermissions) {
this.$store.set('page/effectivePermissions',JSON.parse(Buffer.from(this.effectivePermissions, 'base64').toString())) this.$store.set('page/effectivePermissions', JSON.parse(Buffer.from(this.effectivePermissions, 'base64').toString()))
} }
}, },
methods: { methods: {
@ -86,6 +86,7 @@ export default {
.source { .source {
pre > code { pre > code {
box-shadow: none; box-shadow: none;
background-color: transparent;
color: mc('grey', '800'); color: mc('grey', '800');
font-family: 'Roboto Mono', sans-serif; font-family: 'Roboto Mono', sans-serif;
font-weight: 400; font-weight: 400;

@ -250,6 +250,22 @@ export default {
[{name: this.$t('tags:localeAny'), code: 'any'}], [{name: this.$t('tags:localeAny'), code: 'any'}],
(siteLangs.length > 0 ? siteLangs : []) (siteLangs.length > 0 ? siteLangs : [])
) )
if (this.$route.query.lang) {
this.locale = this.$route.query.lang
}
if (this.$route.query.sort) {
this.orderBy = this.$route.query.sort.toLowerCase()
switch (this.orderBy) {
case 'updatedat':
this.orderBy = 'updatedAt'
break
}
this.pagination.sortBy = [this.orderBy]
}
if (this.$route.query.dir) {
this.orderByDirection = this.$route.query.dir === 'asc' ? 0 : 1
this.pagination.sortDesc = [this.orderByDirection === 1]
}
}, },
methods: { methods: {
toggleTag (tag) { toggleTag (tag) {

@ -37,7 +37,22 @@ export default {
} }
}, },
mounted () { mounted () {
// Handle scroll to header on load within hidden tab content
if (window.location.hash && window.location.hash.length > 1) {
const headerId = decodeURIComponent(window.location.hash)
let foundIdx = -1
this.$refs.content.childNodes.forEach((node, idx) => {
if (node.querySelector(headerId)) {
foundIdx = idx
}
})
if (foundIdx >= 0) {
this.currentTab = foundIdx
}
}
this.setActiveTab() this.setActiveTab()
this.$refs.tabs.childNodes.forEach((node, idx) => { this.$refs.tabs.childNodes.forEach((node, idx) => {
node.addEventListener('click', ev => { node.addEventListener('click', ev => {
this.currentTab = [].indexOf.call(ev.target.parentNode.children, ev.target) this.currentTab = [].indexOf.call(ev.target.parentNode.children, ev.target)

@ -77,6 +77,10 @@
& + h2, & + h3, & + h4, & + h5, & + h6 { & + h2, & + h3, & + h4, & + h5, & + h6 {
margin-top: 8px; margin-top: 8px;
} }
& + hr.footnotes-sep {
display: none;
}
} }
h1 { h1 {
@ -224,6 +228,19 @@
margin: 0 1px -4px; margin: 0 1px -4px;
} }
.text-huge {
font-size: 1.8em;
}
.text-big {
font-size: 1.4em;
}
.text-small {
font-size: .85em;
}
.text-tiny {
font-size: .7em;
}
blockquote { blockquote {
padding: 0 1rem 1rem 1rem; padding: 0 1rem 1rem 1rem;
background-color: mc('blue-grey', '50'); background-color: mc('blue-grey', '50');
@ -504,7 +521,7 @@
} }
} }
ul:not(.tabset-tabs) { ul:not(.tabset-tabs):not(.contains-task-list) {
list-style: none; list-style: none;
> li::before { > li::before {
position: absolute; position: absolute;
@ -583,6 +600,7 @@
.diagram { .diagram {
margin-top: 1rem; margin-top: 1rem;
overflow: auto;
svg:first-child { svg:first-child {
direction: ltr; direction: ltr;
} }
@ -592,21 +610,25 @@
// TASK LISTS // TASK LISTS
// --------------------------------- // ---------------------------------
.contains-task-list {
padding-left: 0;
}
.task-list-item { .task-list-item {
position: relative; position: relative;
list-style-type: none; list-style-type: none;
&-checkbox[disabled] { &-checkbox[disabled] {
display: none; width: 1.1rem;
height: 1.1rem;
& + label { top: 2px;
padding-left: 1.5rem; position: relative;
} margin-right: 2px;
& + label::before { &::after {
position: absolute; position: absolute;
left: 0; left: 0;
top: 2px; top: 0;
content: ' '; content: ' ';
display: block; display: block;
width: 1.1rem; width: 1.1rem;
@ -625,7 +647,7 @@
} }
} }
&[checked] + label::before { &[checked]::after {
content: ''; content: '';
} }
} }
@ -800,26 +822,38 @@
padding-left: 0; padding-left: 0;
} }
summary { > summary {
border-radius: 7px; border-radius: 7px;
background-color: mc('grey', '50'); background-color: mc('grey', '50');
cursor: pointer; cursor: pointer;
height: 40px; display: list-item;
display: flex;
align-items: center; align-items: center;
padding: 0 1rem; padding: 0.4rem 1rem;
transition: background-color .4s ease; transition: background-color .4s ease;
&:focus { &:focus {
outline: none; outline: none;
background-color: mc('grey', '100'); background-color: mc('grey', '100');
} }
> h1, h2, h3, h4, h5, h6 {
width: 95%;
display: inline-block;
&:first-child {
margin-top: 0;
}
&:only-child::after {
display: none;
}
}
} }
&[open] { &[open] {
padding: 1rem; padding: 1rem;
summary { > summary {
background-color: mc('grey', '100'); background-color: mc('grey', '100');
border-bottom: 1px solid mc('grey', '300'); border-bottom: 1px solid mc('grey', '300');
border-bottom-left-radius: 0; border-bottom-left-radius: 0;
@ -832,12 +866,12 @@
background-color: mc('grey', '900'); background-color: mc('grey', '900');
border-color: mc('grey', '700'); border-color: mc('grey', '700');
summary { > summary {
background-color: mc('grey', '900'); background-color: mc('grey', '900');
border-color: mc('grey', '700'); border-color: mc('grey', '700');
} }
&[open] summary { &[open] > summary {
background-color: lighten(mc('grey', '900'), 5%); background-color: lighten(mc('grey', '900'), 5%);
} }
} }

@ -1,9 +1,7 @@
# ========================= # =========================
# --- BUILD NPM MODULES --- # --- BUILD NPM MODULES ---
# ========================= # =========================
FROM node:14-alpine AS build FROM node:14 AS build
RUN apk add yarn g++ make python --no-cache
WORKDIR /wiki WORKDIR /wiki

@ -1,9 +1,9 @@
# ==================== # ====================
# --- Build Assets --- # --- Build Assets ---
# ==================== # ====================
FROM node:14-alpine AS assets FROM node:16-alpine AS assets
RUN apk add yarn g++ make python --no-cache RUN apk add yarn g++ make --no-cache
WORKDIR /wiki WORKDIR /wiki
@ -23,7 +23,7 @@ RUN yarn --production --frozen-lockfile --non-interactive
# =============== # ===============
# --- Release --- # --- Release ---
# =============== # ===============
FROM node:14-alpine FROM node:16-alpine
LABEL maintainer="requarks.io" LABEL maintainer="requarks.io"
RUN apk add bash curl git openssh gnupg sqlite --no-cache && \ RUN apk add bash curl git openssh gnupg sqlite --no-cache && \

@ -29,7 +29,7 @@ dependencies:
repository: https://charts.bitnami.com/bitnami repository: https://charts.bitnami.com/bitnami
condition: postgresql.enabled condition: postgresql.enabled
home: https://wiki.js.org home: https://wiki.js.org
icon: https://github.com/Requarks/wiki/raw/master/client/static/favicons/android-chrome-192x192.png icon: https://cdn.js.wiki/images/wikijs-butterfly.svg
sources: sources:
- https://github.com/Requarks/wiki - https://github.com/Requarks/wiki
maintainers: maintainers:

@ -49,17 +49,23 @@ It also optionally packages the [PostgreSQL](https://github.com/kubernetes/chart
- PV provisioner support in the underlying infrastructure (with persistence storage enabled) if you want data persistance - PV provisioner support in the underlying infrastructure (with persistence storage enabled) if you want data persistance
## Adding the Wiki.js Helm Repository
```console
$ helm repo add requarks https://charts.js.wiki
```
## Installing the Chart ## Installing the Chart
To install the chart with the release name `my-release` run the following from this (`helm`) directory: To install the chart with the release name `my-release` run the following:
### Using Helm 3: ### Using Helm 3:
```console ```console
$ helm install my-release . $ helm install my-release requarks/wiki
``` ```
### Using Helm 2: ### Using Helm 2:
```console ```console
$ helm install --name my-release . $ helm install --name my-release requarks/wiki
``` ```
The command deploys Wiki.js on the Kubernetes cluster in the default configuration. The [configuration](#configuration) section lists the parameters that can be configured during installation. The command deploys Wiki.js on the Kubernetes cluster in the default configuration. The [configuration](#configuration) section lists the parameters that can be configured during installation.
@ -102,6 +108,9 @@ The following table lists the configurable parameters of the Wiki.js chart and t
| `ingress.annotations` | Ingress annotations | `{}` | | `ingress.annotations` | Ingress annotations | `{}` |
| `ingress.hosts` | List of ingress rules | `[{"host": "wiki.local", "paths": ["/"]}]` | | `ingress.hosts` | List of ingress rules | `[{"host": "wiki.local", "paths": ["/"]}]` |
| `ingress.tls` | Ingress TLS configuration | `[]` | | `ingress.tls` | Ingress TLS configuration | `[]` |
| `sideload.enabled` | Enable sideloading of locale files from git | `false` |
| `sideload.repoURL` | Git repository URL containing locale files | `https://github.com/Requarks/wiki-localization` |
| `sideload.env` | Environment variables for sideload Container | `{}` |
| `postgresql.enabled` | Deploy postgres server (see below) | `true` | | `postgresql.enabled` | Deploy postgres server (see below) | `true` |
| `postgresql.postgresqlDatabase` | Postgres database name | `wiki` | | `postgresql.postgresqlDatabase` | Postgres database name | `wiki` |
| `postgresql.postgresqlUser` | Postgres username | `postgres` | | `postgresql.postgresqlUser` | Postgres username | `postgres` |
@ -111,7 +120,7 @@ The following table lists the configurable parameters of the Wiki.js chart and t
| `postgresql.existingSecretKey` | The postgres password key in the existing `Secret` | `postgresql-password` | | `postgresql.existingSecretKey` | The postgres password key in the existing `Secret` | `postgresql-password` |
| `postgresql.postgresqlPort` | External postgres port | `5432` | | `postgresql.postgresqlPort` | External postgres port | `5432` |
| `postgresql.ssl` | Enable external postgres SSL connection | `false` | | `postgresql.ssl` | Enable external postgres SSL connection | `false` |
| `postgresql.ca` | Certificate of Authority path for postgres | `nil` | | `postgresql.ca` | Certificate of Authority content for postgres | `nil` |
| `postgresql.persistence.enabled` | Enable postgres persistence using PVC | `true` | | `postgresql.persistence.enabled` | Enable postgres persistence using PVC | `true` |
| `postgresql.persistence.existingClaim` | Provide an existing `PersistentVolumeClaim` for postgres | `nil` | | `postgresql.persistence.existingClaim` | Provide an existing `PersistentVolumeClaim` for postgres | `nil` |
| `postgresql.persistence.storageClass` | Postgres PVC Storage Class (example: `nfs`) | `nil` | | `postgresql.persistence.storageClass` | Postgres PVC Storage Class (example: `nfs`) | `nil` |
@ -122,13 +131,13 @@ Specify each parameter using the `--set key=value[,key=value]` argument to `helm
```console ```console
$ helm install --name my-release \ $ helm install --name my-release \
--set postgresql.persistence.enabled=false \ --set postgresql.persistence.enabled=false \
. requarks/wiki
``` ```
Alternatively, a YAML file that specifies the values for the above parameters can be provided while installing the chart. For example, Alternatively, a YAML file that specifies the values for the above parameters can be provided while installing the chart. For example,
```console ```console
$ helm install --name my-release -f values.yaml . $ helm install --name my-release -f values.yaml requarks/wiki
``` ```
> **Tip**: You can use the default [values.yaml](values.yaml) > **Tip**: You can use the default [values.yaml](values.yaml)

@ -21,6 +21,16 @@ spec:
serviceAccountName: {{ include "wiki.serviceAccountName" . }} serviceAccountName: {{ include "wiki.serviceAccountName" . }}
securityContext: securityContext:
{{- toYaml .Values.podSecurityContext | nindent 8 }} {{- toYaml .Values.podSecurityContext | nindent 8 }}
{{- if .Values.sideload.enabled }}
initContainers:
- name: {{ .Chart.Name }}-sideload
image: "{{ .Values.image.repository }}:{{ default "latest" .Values.image.tag }}"
imagePullPolicy: {{ default "IfNotPresent" .Values.image.imagePullPolicy }}
env:
{{- toYaml .Values.sideload.env | nindent 12 }}
command: [ "sh", "-c" ]
args: [ "mkdir -p /wiki/data/sideload && git clone --depth=1 {{ .Values.sideload.repoURL }} /wiki/data/sideload/" ]
{{- end }}
containers: containers:
- name: {{ .Chart.Name }} - name: {{ .Chart.Name }}
securityContext: securityContext:
@ -51,7 +61,7 @@ spec:
name: {{ template "wiki.postgresql.secret" . }} name: {{ template "wiki.postgresql.secret" . }}
{{- end }} {{- end }}
key: {{ template "wiki.postgresql.secretKey" . }} key: {{ template "wiki.postgresql.secretKey" . }}
- name: HA - name: HA_ACTIVE
value: {{ .Values.replicaCount | int | le 2 | quote }} value: {{ .Values.replicaCount | int | le 2 | quote }}
ports: ports:
- name: http - name: http

@ -6,7 +6,7 @@ replicaCount: 1
image: image:
repository: requarks/wiki repository: requarks/wiki
pullPolicy: IfNotPresent imagePullPolicy: IfNotPresent
imagePullSecrets: [] imagePullSecrets: []
nameOverride: "" nameOverride: ""
@ -85,6 +85,17 @@ tolerations: []
affinity: {} affinity: {}
# This will allow us to install locales even without internet access using a initContainer & wikjs "sideloading"
sideload:
enabled: false
# Git-Repo containing all locales.json-files you need:
repoURL: https://github.com/Requarks/wiki-localization
## This can be helpfull if you have internet access over a http proxy:
env: []
# - name: HTTPS_PROXY
# value: http://my.proxy.com:3128
## Configuration values for the postgresql dependency. ## Configuration values for the postgresql dependency.
## ref: https://github.com/kubernetes/charts/blob/master/stable/postgresql/README.md ## ref: https://github.com/kubernetes/charts/blob/master/stable/postgresql/README.md
## ##

@ -84,7 +84,10 @@ module.exports = {
loader: 'sass-loader', loader: 'sass-loader',
options: { options: {
implementation: require('sass'), implementation: require('sass'),
sourceMap: false sourceMap: false,
sassOptions: {
fiber: false
}
} }
} }
] ]
@ -105,7 +108,10 @@ module.exports = {
loader: 'sass-loader', loader: 'sass-loader',
options: { options: {
implementation: require('sass'), implementation: require('sass'),
sourceMap: false sourceMap: false,
sassOptions: {
fiber: false
}
} }
}, },
{ {

@ -89,7 +89,10 @@ module.exports = {
loader: 'sass-loader', loader: 'sass-loader',
options: { options: {
implementation: require('sass'), implementation: require('sass'),
sourceMap: false sourceMap: false,
sassOptions: {
fiber: false
}
} }
} }
] ]
@ -111,7 +114,10 @@ module.exports = {
loader: 'sass-loader', loader: 'sass-loader',
options: { options: {
implementation: require('sass'), implementation: require('sass'),
sourceMap: false sourceMap: false,
sassOptions: {
fiber: false
}
} }
}, },
{ {

@ -38,31 +38,31 @@
"dependencies": { "dependencies": {
"@azure/storage-blob": "12.2.1", "@azure/storage-blob": "12.2.1",
"@exlinc/keycloak-passport": "1.0.2", "@exlinc/keycloak-passport": "1.0.2",
"@joplin/turndown-plugin-gfm": "1.0.27", "@joplin/turndown-plugin-gfm": "1.0.43",
"@root/csr": "0.8.1", "@root/csr": "0.8.1",
"@root/keypairs": "0.10.1", "@root/keypairs": "0.10.3",
"@root/pem": "1.0.4", "@root/pem": "1.0.4",
"acme": "3.0.3", "acme": "3.0.3",
"akismet-api": "5.1.0", "akismet-api": "5.2.1",
"algoliasearch": "4.5.1", "algoliasearch": "4.5.1",
"apollo-fetch": "0.7.0", "apollo-fetch": "0.7.0",
"apollo-server": "2.18.2", "apollo-server": "2.25.2",
"apollo-server-express": "2.18.2", "apollo-server-express": "2.25.2",
"auto-load": "3.0.4", "auto-load": "3.0.4",
"aws-sdk": "2.778.0", "aws-sdk": "2.1043.0",
"azure-search-client": "3.1.5", "azure-search-client": "3.1.5",
"bcryptjs-then": "1.0.1", "bcryptjs-then": "1.0.1",
"bluebird": "3.7.2", "bluebird": "3.7.2",
"body-parser": "1.19.0", "body-parser": "1.19.1",
"chalk": "4.1.0", "chalk": "4.1.0",
"cheerio": "1.0.0-rc.5", "cheerio": "1.0.0-rc.5",
"chokidar": "3.4.3", "chokidar": "3.5.3",
"chromium-pickle-js": "0.2.0", "chromium-pickle-js": "0.2.0",
"clean-css": "4.2.3", "clean-css": "4.2.3",
"command-exists": "1.2.9", "command-exists": "1.2.9",
"compression": "1.7.4", "compression": "1.7.4",
"connect-session-knex": "2.0.0", "connect-session-knex": "2.0.0",
"cookie-parser": "1.4.5", "cookie-parser": "1.4.6",
"cors": "2.8.5", "cors": "2.8.5",
"cuint": "0.2.2", "cuint": "0.2.2",
"custom-error-instance": "2.1.2", "custom-error-instance": "2.1.2",
@ -74,10 +74,10 @@
"elasticsearch6": "npm:@elastic/elasticsearch@6", "elasticsearch6": "npm:@elastic/elasticsearch@6",
"elasticsearch7": "npm:@elastic/elasticsearch@7", "elasticsearch7": "npm:@elastic/elasticsearch@7",
"emoji-regex": "9.2.2", "emoji-regex": "9.2.2",
"eventemitter2": "6.4.4", "eventemitter2": "6.4.5",
"express": "4.17.1", "express": "4.17.2",
"express-brute": "1.0.1", "express-brute": "1.0.1",
"express-session": "1.17.1", "express-session": "1.17.2",
"file-type": "15.0.1", "file-type": "15.0.1",
"filesize": "6.1.0", "filesize": "6.1.0",
"fs-extra": "9.0.1", "fs-extra": "9.0.1",
@ -94,7 +94,7 @@
"i18next-express-middleware": "2.0.0", "i18next-express-middleware": "2.0.0",
"i18next-node-fs-backend": "2.1.3", "i18next-node-fs-backend": "2.1.3",
"image-size": "0.9.2", "image-size": "0.9.2",
"js-base64": "3.5.2", "js-base64": "3.7.2",
"js-binary": "1.2.0", "js-binary": "1.2.0",
"js-yaml": "3.14.0", "js-yaml": "3.14.0",
"jsdom": "16.4.0", "jsdom": "16.4.0",
@ -110,7 +110,7 @@
"markdown-it-emoji": "1.4.0", "markdown-it-emoji": "1.4.0",
"markdown-it-expand-tabs": "1.0.13", "markdown-it-expand-tabs": "1.0.13",
"markdown-it-external-links": "0.0.6", "markdown-it-external-links": "0.0.6",
"markdown-it-footnote": "3.0.2", "markdown-it-footnote": "3.0.3",
"markdown-it-imsize": "2.0.1", "markdown-it-imsize": "2.0.1",
"markdown-it-mark": "3.0.1", "markdown-it-mark": "3.0.1",
"markdown-it-mathjax": "2.0.0", "markdown-it-mathjax": "2.0.0",
@ -119,22 +119,22 @@
"markdown-it-sup": "1.0.0", "markdown-it-sup": "1.0.0",
"markdown-it-task-lists": "2.1.1", "markdown-it-task-lists": "2.1.1",
"mathjax": "3.1.2", "mathjax": "3.1.2",
"mime-types": "2.1.30", "mime-types": "2.1.34",
"moment": "2.29.1", "moment": "2.29.1",
"moment-timezone": "0.5.31", "moment-timezone": "0.5.31",
"mongodb": "3.6.5", "mongodb": "3.6.5",
"ms": "2.1.3", "ms": "2.1.3",
"mssql": "6.2.3", "mssql": "6.2.3",
"multer": "1.4.2", "multer": "1.4.4",
"mysql2": "2.2.5", "mysql2": "2.3.3",
"nanoid": "3.1.22", "nanoid": "3.2.0",
"node-2fa": "1.1.2", "node-2fa": "1.1.2",
"node-cache": "5.1.2", "node-cache": "5.1.2",
"nodemailer": "6.4.14", "nodemailer": "6.7.2",
"objection": "2.2.3", "objection": "2.2.18",
"passport": "0.4.1", "passport": "0.4.1",
"passport-auth0": "1.4.0", "passport-auth0": "1.4.2",
"passport-azure-ad": "4.3.0", "passport-azure-ad": "4.3.1",
"passport-cas": "0.1.1", "passport-cas": "0.1.1",
"passport-discord": "0.1.4", "passport-discord": "0.1.4",
"passport-dropbox-oauth2": "1.1.0", "passport-dropbox-oauth2": "1.1.0",
@ -146,7 +146,7 @@
"passport-ldapauth": "2.1.4", "passport-ldapauth": "2.1.4",
"passport-local": "1.0.0", "passport-local": "1.0.0",
"passport-microsoft": "0.1.0", "passport-microsoft": "0.1.0",
"passport-oauth2": "1.5.0", "passport-oauth2": "1.6.1",
"passport-okta-oauth": "0.0.1", "passport-okta-oauth": "0.0.1",
"passport-openidconnect": "0.0.2", "passport-openidconnect": "0.0.2",
"passport-saml": "1.3.5", "passport-saml": "1.3.5",
@ -154,7 +154,7 @@
"passport-twitch-oauth": "1.0.0", "passport-twitch-oauth": "1.0.0",
"pem-jwk": "2.0.0", "pem-jwk": "2.0.0",
"pg": "8.4.1", "pg": "8.4.1",
"pg-hstore": "2.3.3", "pg-hstore": "2.3.4",
"pg-pubsub": "0.5.0", "pg-pubsub": "0.5.0",
"pg-query-stream": "3.3.1", "pg-query-stream": "3.3.1",
"pg-tsquery": "8.1.0", "pg-tsquery": "8.1.0",
@ -173,18 +173,18 @@
"simple-git": "2.21.0", "simple-git": "2.21.0",
"solr-node": "1.2.1", "solr-node": "1.2.1",
"sqlite3": "5.0.2", "sqlite3": "5.0.2",
"ssh2": "0.8.9", "ssh2": "1.5.0",
"ssh2-promise": "0.1.7", "ssh2-promise": "1.0.2",
"striptags": "3.1.1", "striptags": "3.2.0",
"subscriptions-transport-ws": "0.9.18", "subscriptions-transport-ws": "0.9.18",
"tar-fs": "2.1.1", "tar-fs": "2.1.1",
"turndown": "7.0.0", "turndown": "7.1.1",
"twemoji": "13.0.2", "twemoji": "13.1.0",
"uslug": "1.0.4", "uslug": "1.0.4",
"uuid": "8.3.2", "uuid": "8.3.2",
"validate.js": "0.13.1", "validate.js": "0.13.1",
"winston": "3.3.3", "winston": "3.3.3",
"xss": "1.0.8", "xss": "1.0.10",
"yargs": "16.1.0" "yargs": "16.1.0"
}, },
"devDependencies": { "devDependencies": {
@ -204,7 +204,7 @@
"@mdi/font": "5.8.55", "@mdi/font": "5.8.55",
"@panter/vue-i18next": "0.15.2", "@panter/vue-i18next": "0.15.2",
"@requarks/ckeditor5": "19.0.1-wiki.2", "@requarks/ckeditor5": "19.0.1-wiki.2",
"@vue/babel-preset-app": "4.5.8", "@vue/babel-preset-app": "4.5.15",
"animate-sass": "0.8.2", "animate-sass": "0.8.2",
"animated-number-vue": "1.0.0", "animated-number-vue": "1.0.0",
"apollo-cache-inmemory": "1.6.6", "apollo-cache-inmemory": "1.6.6",
@ -248,10 +248,9 @@
"eslint-plugin-promise": "4.2.1", "eslint-plugin-promise": "4.2.1",
"eslint-plugin-standard": "4.0.2", "eslint-plugin-standard": "4.0.2",
"eslint-plugin-vue": "7.1.0", "eslint-plugin-vue": "7.1.0",
"fibers": "5.0.0",
"file-loader": "6.1.1", "file-loader": "6.1.1",
"filepond": "4.21.1", "filepond": "4.21.1",
"filepond-plugin-file-validate-type": "1.2.5", "filepond-plugin-file-validate-type": "1.2.6",
"filesize.js": "2.0.0", "filesize.js": "2.0.0",
"graphql-persisted-document-loader": "2.0.0", "graphql-persisted-document-loader": "2.0.0",
"graphql-tag": "2.11.0", "graphql-tag": "2.11.0",
@ -259,7 +258,7 @@
"html-webpack-plugin": "4.5.0", "html-webpack-plugin": "4.5.0",
"html-webpack-pug-plugin": "2.0.0", "html-webpack-pug-plugin": "2.0.0",
"i18next-chained-backend": "2.0.1", "i18next-chained-backend": "2.0.1",
"i18next-localstorage-backend": "3.1.2", "i18next-localstorage-backend": "3.1.3",
"i18next-xhr-backend": "3.2.2", "i18next-xhr-backend": "3.2.2",
"ignore-loader": "0.1.2", "ignore-loader": "0.1.2",
"jest": "26.6.1", "jest": "26.6.1",
@ -272,13 +271,13 @@
"offline-plugin": "5.0.7", "offline-plugin": "5.0.7",
"optimize-css-assets-webpack-plugin": "5.0.4", "optimize-css-assets-webpack-plugin": "5.0.4",
"pako": "1.0.11", "pako": "1.0.11",
"postcss-cssnext": "3.1.0", "postcss-cssnext": "3.1.1",
"postcss-flexbugs-fixes": "4.2.1", "postcss-flexbugs-fixes": "4.2.1",
"postcss-flexibility": "2.0.0", "postcss-flexibility": "2.0.0",
"postcss-import": "12.0.1", "postcss-import": "12.0.1",
"postcss-loader": "3.0.0", "postcss-loader": "3.0.0",
"postcss-preset-env": "6.7.0", "postcss-preset-env": "6.7.0",
"postcss-selector-parser": "6.0.4", "postcss-selector-parser": "6.0.9",
"prismjs": "1.22.0", "prismjs": "1.22.0",
"pug-lint": "2.6.0", "pug-lint": "2.6.0",
"pug-loader": "2.4.0", "pug-loader": "2.4.0",
@ -288,7 +287,7 @@
"sass": "1.27.0", "sass": "1.27.0",
"sass-loader": "10.0.4", "sass-loader": "10.0.4",
"sass-resources-loader": "2.1.1", "sass-resources-loader": "2.1.1",
"script-ext-html-webpack-plugin": "2.1.4", "script-ext-html-webpack-plugin": "2.1.5",
"simple-progress-webpack-plugin": "1.1.2", "simple-progress-webpack-plugin": "1.1.2",
"style-loader": "1.3.0", "style-loader": "1.3.0",
"terser": "5.3.8", "terser": "5.3.8",
@ -296,17 +295,17 @@
"url-loader": "4.1.1", "url-loader": "4.1.1",
"velocity-animate": "1.5.2", "velocity-animate": "1.5.2",
"viz.js": "2.1.2", "viz.js": "2.1.2",
"vue": "2.6.12", "vue": "2.6.14",
"vue-apollo": "3.0.5", "vue-apollo": "3.0.5",
"vue-chartjs": "3.5.1", "vue-chartjs": "3.5.1",
"vue-clipboards": "1.3.0", "vue-clipboards": "1.3.0",
"vue-filepond": "6.0.3", "vue-filepond": "6.0.3",
"vue-hot-reload-api": "2.3.4", "vue-hot-reload-api": "2.3.4",
"vue-loader": "15.9.6", "vue-loader": "15.9.8",
"vue-moment": "4.1.0", "vue-moment": "4.1.0",
"vue-router": "3.4.7", "vue-router": "3.4.7",
"vue-status-indicator": "1.2.1", "vue-status-indicator": "1.2.1",
"vue-template-compiler": "2.6.12", "vue-template-compiler": "2.6.14",
"vue2-animate": "2.1.4", "vue2-animate": "2.1.4",
"vuedraggable": "2.24.3", "vuedraggable": "2.24.3",
"vuescroll": "4.16.1", "vuescroll": "4.16.1",
@ -319,18 +318,18 @@
"webpack-bundle-analyzer": "3.9.0", "webpack-bundle-analyzer": "3.9.0",
"webpack-cli": "3.3.12", "webpack-cli": "3.3.12",
"webpack-dev-middleware": "3.7.2", "webpack-dev-middleware": "3.7.2",
"webpack-hot-middleware": "2.25.0", "webpack-hot-middleware": "2.25.1",
"webpack-merge": "5.2.0", "webpack-merge": "5.2.0",
"webpack-modernizr-loader": "5.0.0", "webpack-modernizr-loader": "5.0.0",
"webpack-subresource-integrity": "1.5.1", "webpack-subresource-integrity": "1.5.1",
"webpackbar": "4.0.0", "webpackbar": "4.0.0",
"whatwg-fetch": "3.4.1", "whatwg-fetch": "3.6.2",
"write-file-webpack-plugin": "4.5.1", "write-file-webpack-plugin": "4.5.1",
"xterm": "4.9.0", "xterm": "4.9.0",
"zxcvbn": "4.4.2" "zxcvbn": "4.4.2"
}, },
"resolutions": { "resolutions": {
"apollo-server-express/**/graphql-tools": "4.0.7", "apollo-server-express/**/graphql-tools": "4.0.8",
"graphql": "15.3.0" "graphql": "15.3.0"
}, },
"browserslist": [ "browserslist": [

@ -80,6 +80,8 @@ defaults:
uploads: uploads:
maxFileSize: 5242880 maxFileSize: 5242880
maxFiles: 10 maxFiles: 10
scanSVG: true
forceDownload: true
flags: flags:
ldapdebug: false ldapdebug: false
sqllog: false sqllog: false

@ -372,7 +372,8 @@ router.get(['/s', '/s/*'], async (req, res, next) => {
page: { page: {
...page, ...page,
...pageVersion ...pageVersion
} },
effectivePermissions
}) })
} else { } else {
_.set(res.locals, 'pageMeta.title', page.title) _.set(res.locals, 'pageMeta.title', page.title)

@ -29,7 +29,9 @@ module.exports = {
authJwtExpiration: WIKI.config.auth.tokenExpiration, authJwtExpiration: WIKI.config.auth.tokenExpiration,
authJwtRenewablePeriod: WIKI.config.auth.tokenRenewal, authJwtRenewablePeriod: WIKI.config.auth.tokenRenewal,
uploadMaxFileSize: WIKI.config.uploads.maxFileSize, uploadMaxFileSize: WIKI.config.uploads.maxFileSize,
uploadMaxFiles: WIKI.config.uploads.maxFiles uploadMaxFiles: WIKI.config.uploads.maxFiles,
uploadScanSVG: WIKI.config.uploads.scanSVG,
uploadForceDownload: WIKI.config.uploads.forceDownload
} }
} }
}, },
@ -97,7 +99,9 @@ module.exports = {
WIKI.config.uploads = { WIKI.config.uploads = {
maxFileSize: _.get(args, 'uploadMaxFileSize', WIKI.config.uploads.maxFileSize), maxFileSize: _.get(args, 'uploadMaxFileSize', WIKI.config.uploads.maxFileSize),
maxFiles: _.get(args, 'uploadMaxFiles', WIKI.config.uploads.maxFiles) maxFiles: _.get(args, 'uploadMaxFiles', WIKI.config.uploads.maxFiles),
scanSVG: _.get(args, 'uploadScanSVG', WIKI.config.uploads.scanSVG),
forceDownload: _.get(args, 'uploadForceDownload', WIKI.config.uploads.forceDownload)
} }
await WIKI.configSvc.saveToDb(['host', 'title', 'company', 'contentLicense', 'seo', 'logoUrl', 'auth', 'features', 'security', 'uploads']) await WIKI.configSvc.saveToDb(['host', 'title', 'company', 'contentLicense', 'seo', 'logoUrl', 'auth', 'features', 'security', 'uploads'])

@ -54,6 +54,8 @@ type SiteMutation {
securityCSPDirectives: String securityCSPDirectives: String
uploadMaxFileSize: Int uploadMaxFileSize: Int
uploadMaxFiles: Int uploadMaxFiles: Int
uploadScanSVG: Boolean
uploadForceDownload: Boolean
): DefaultResponse @auth(requires: ["manage:system"]) ): DefaultResponse @auth(requires: ["manage:system"])
} }
@ -63,15 +65,15 @@ type SiteMutation {
# ----------------------------------------------- # -----------------------------------------------
type SiteConfig { type SiteConfig {
host: String! host: String
title: String! title: String
description: String! description: String
robots: [String]! robots: [String]
analyticsService: String! analyticsService: String
analyticsId: String! analyticsId: String
company: String! company: String
contentLicense: String! contentLicense: String
logoUrl: String! logoUrl: String
authAutoLogin: Boolean authAutoLogin: Boolean
authEnforce2FA: Boolean authEnforce2FA: Boolean
authHideLocal: Boolean authHideLocal: Boolean
@ -79,18 +81,20 @@ type SiteConfig {
authJwtAudience: String authJwtAudience: String
authJwtExpiration: String authJwtExpiration: String
authJwtRenewablePeriod: String authJwtRenewablePeriod: String
featurePageRatings: Boolean! featurePageRatings: Boolean
featurePageComments: Boolean! featurePageComments: Boolean
featurePersonalWikis: Boolean! featurePersonalWikis: Boolean
securityOpenRedirect: Boolean! securityOpenRedirect: Boolean
securityIframe: Boolean! securityIframe: Boolean
securityReferrerPolicy: Boolean! securityReferrerPolicy: Boolean
securityTrustProxy: Boolean! securityTrustProxy: Boolean
securitySRI: Boolean! securitySRI: Boolean
securityHSTS: Boolean! securityHSTS: Boolean
securityHSTSDuration: Int! securityHSTSDuration: Int
securityCSP: Boolean! securityCSP: Boolean
securityCSPDirectives: String! securityCSPDirectives: String
uploadMaxFileSize: Int! uploadMaxFileSize: Int
uploadMaxFiles: Int! uploadMaxFiles: Int
uploadScanSVG: Boolean
uploadForceDownload: Boolean
} }

@ -1,4 +1,5 @@
const crypto = require('crypto') const crypto = require('crypto')
const path = require('path')
module.exports = { module.exports = {
/** /**
@ -6,5 +7,9 @@ module.exports = {
*/ */
generateHash(assetPath) { generateHash(assetPath) {
return crypto.createHash('sha1').update(assetPath).digest('hex') return crypto.createHash('sha1').update(assetPath).digest('hex')
},
getPathInfo(assetPath) {
return path.parse(assetPath.toLowerCase())
} }
} }

@ -35,6 +35,8 @@ module.exports = {
rawPath = rawPath.replace(unsafeCharsRegex, '') rawPath = rawPath.replace(unsafeCharsRegex, '')
if (rawPath === '') { rawPath = 'home' } if (rawPath === '') { rawPath = 'home' }
rawPath = rawPath.replace(/\\/g, '').replace(/\/\//g, '').replace(/\.\.+/ig, '')
// Extract Info // Extract Info
let pathParts = _.filter(_.split(rawPath, '/'), p => { let pathParts = _.filter(_.split(rawPath, '/'), p => {
p = _.trim(p) p = _.trim(p)

@ -31,6 +31,10 @@ module.exports = {
if (req && req.cookies) { if (req && req.cookies) {
token = req.cookies['jwt'] token = req.cookies['jwt']
} }
// Force uploads to use Auth headers
if (req.path.toLowerCase() === '/u') {
return null
}
return token return token
} }
]) ])

@ -0,0 +1,25 @@
const fs = require('fs-extra')
const { JSDOM } = require('jsdom')
const createDOMPurify = require('dompurify')
/* global WIKI */
module.exports = async (svgPath) => {
WIKI.logger.info(`Sanitizing SVG file upload...`)
try {
let svgContents = await fs.readFile(svgPath, 'utf8')
const window = new JSDOM('').window
const DOMPurify = createDOMPurify(window)
svgContents = DOMPurify.sanitize(svgContents)
await fs.writeFile(svgPath, svgContents)
WIKI.logger.info(`Sanitized SVG file upload: [ COMPLETED ]`)
} catch (err) {
WIKI.logger.error(`Failed to sanitize SVG file upload: [ FAILED ]`)
WIKI.logger.error(err.message)
throw err
}
}

@ -99,6 +99,22 @@ module.exports = class Asset extends Model {
folderId: opts.folderId folderId: opts.folderId
} }
// Sanitize SVG contents
if (
WIKI.config.uploads.scanSVG &&
(
opts.mimetype.toLowerCase().startsWith('image/svg') ||
fileInfo.ext.toLowerCase() === '.svg'
)
) {
const svgSanitizeJob = await WIKI.scheduler.registerJob({
name: 'sanitize-svg',
immediate: true,
worker: true
}, opts.path)
await svgSanitizeJob.finished
}
// Save asset data // Save asset data
try { try {
const fileBuffer = await fs.readFile(opts.path) const fileBuffer = await fs.readFile(opts.path)
@ -152,8 +168,15 @@ module.exports = class Asset extends Model {
static async getAsset(assetPath, res) { static async getAsset(assetPath, res) {
try { try {
const fileInfo = assetHelper.getPathInfo(assetPath)
const fileHash = assetHelper.generateHash(assetPath) const fileHash = assetHelper.generateHash(assetPath)
const cachePath = path.resolve(WIKI.ROOTPATH, WIKI.config.dataPath, `cache/${fileHash}.dat`) const cachePath = path.resolve(WIKI.ROOTPATH, WIKI.config.dataPath, `cache/${fileHash}.dat`)
// Force unsafe extensions to download
if (WIKI.config.uploads.forceDownload && !['.png', '.apng', '.jpg', '.jpeg', '.gif', '.bmp', '.webp', '.svg'].includes(fileInfo.ext)) {
res.set('Content-disposition', 'attachment; filename=' + fileInfo.base)
}
if (await WIKI.models.assets.getAssetFromCache(assetPath, cachePath, res)) { if (await WIKI.models.assets.getAssetFromCache(assetPath, cachePath, res)) {
return return
} }

@ -0,0 +1,2 @@
head: |
<script defer data-domain="{{domain}}" src="{{plausibleJsSrc}}"></script>

@ -0,0 +1,19 @@
key: plausible
title: Plausible Analytics
description: Simple, open-source, lightweight and privacy-friendly web analytics alternative to Google Analytics.
author: requarks.io
logo: https://cdn.js.wiki/images/3rdparty/plausible.svg
website: https://plausible.io
isAvailable: true
props:
domain:
type: String
title: Domain
hint: The value of the data-domain property
order: 1
plausibleJsSrc:
type: String
default: https://plausible.io/js/plausible.js
title: Plausible JS Script source
hint: The URL of Plausbile Script (only needed when using a self hosted installation)
order: 2

@ -0,0 +1,61 @@
const _ = require('lodash')
/* global WIKI */
// ------------------------------------
// OAuth2 Account
// ------------------------------------
const OAuth2Strategy = require('passport-oauth2').Strategy
module.exports = {
init (passport, conf) {
var client = new OAuth2Strategy({
authorizationURL: conf.authorizationURL,
tokenURL: conf.tokenURL,
clientID: conf.clientId,
clientSecret: conf.clientSecret,
userInfoURL: conf.userInfoURL,
callbackURL: conf.callbackURL,
passReqToCallback: true
}, async (req, accessToken, refreshToken, profile, cb) => {
try {
const user = await WIKI.models.users.processProfile({
providerKey: req.params.strategy,
profile: {
...profile,
id: _.get(profile, conf.userIdClaim),
displayName: _.get(profile, conf.displayNameClaim, '???'),
email: _.get(profile, conf.emailClaim)
}
})
cb(null, user)
} catch (err) {
cb(err, null)
}
})
client.userProfile = function (accesstoken, done) {
this._oauth2._useAuthorizationHeaderForGET = true
this._oauth2.get(conf.userInfoURL, accesstoken, (err, data) => {
if (err) {
return done(err)
}
try {
data = JSON.parse(data)
} catch (e) {
return done(e)
}
done(null, data)
})
}
passport.use('oauth2', client)
},
logout (conf) {
if (!conf.logoutURL) {
return '/'
} else {
return conf.logoutURL
}
}
}

@ -0,0 +1,61 @@
key: oauth2
title: Generic OAuth2
description: OAuth 2.0 is the industry-standard protocol for authorization.
author: requarks.io
logo: https://static.requarks.io/logo/oauth2.svg
color: blue-grey darken-2
website: https://oauth.net/2/
isAvailable: true
useForm: false
props:
clientId:
type: String
title: Client ID
hint: Application Client ID
order: 1
clientSecret:
type: String
title: Client Secret
hint: Application Client Secret
order: 2
authorizationURL:
type: String
title: Authorization Endpoint URL
hint: Application Authorization Endpoint URL
order: 3
tokenURL:
type: String
title: Token Endpoint URL
hint: Application Token Endpoint URL
order: 4
userInfoURL:
type: String
title: User Info Endpoint URL
hint: User Info Endpoint URL
order: 5
userIdClaim:
type: String
title: ID Claim
hint: Field containing the user ID
default: id
maxWidth: 500
order: 6
displayNameClaim:
type: String
title: Display Name Claim
hint: Field containing user display name
default: displayName
maxWidth: 500
order: 7
emailClaim:
type: String
title: Email Claim
hint: Field containing the user email address
default: email
maxWidth: 500
order: 8
logoutURL:
type: String
title: Logout URL
hint: (optional) Logout URL on the OAuth2 provider where the user will be redirected to complete the logout process.
order: 9

@ -23,7 +23,7 @@ module.exports = {
cb(null, { cb(null, {
id: usr._id, id: usr._id,
displayName: _.isEmpty(usr.name) ? usr.username : usr.name, displayName: _.isEmpty(usr.name) ? usr.username : usr.name,
email: usr.email, email: usr.emails[0].address,
picture: usr.avatarUrl picture: usr.avatarUrl
}) })
} catch (err) { } catch (err) {

@ -201,10 +201,11 @@ module.exports = {
let headers = [] let headers = []
$('h1,h2,h3,h4,h5,h6').each((i, elm) => { $('h1,h2,h3,h4,h5,h6').each((i, elm) => {
let headerSlug = uslug($(elm).text())
// -> If custom ID is defined, try to use that instead
if ($(elm).attr('id')) { if ($(elm).attr('id')) {
return headerSlug = $(elm).attr('id')
} }
let headerSlug = uslug($(elm).text())
// -> Cannot start with a number (CSS selector limitation) // -> Cannot start with a number (CSS selector limitation)
if (headerSlug.match(/^\d/)) { if (headerSlug.match(/^\d/)) {
@ -233,11 +234,11 @@ module.exports = {
}) })
// -------------------------------- // --------------------------------
// Wrap root text nodes // Wrap non-empty root text nodes
// -------------------------------- // --------------------------------
$('body').contents().toArray().forEach(item => { $('body').contents().toArray().forEach(item => {
if (item && item.type === 'text' && item.parent.name === 'body') { if (item && item.type === 'text' && item.parent.name === 'body' && item.data !== `\n` && item.data !== `\r`) {
$(item).wrap('<div></div>') $(item).wrap('<div></div>')
} }
}) })

@ -181,7 +181,7 @@ module.exports = {
if (!item.binary && contentType) { if (!item.binary && contentType) {
// -> Page // -> Page
if (fileExists && item.relPath !== item.oldPath) { if (fileExists && !item.importAll && item.relPath !== item.oldPath) {
// Page was renamed by git, so rename in DB // Page was renamed by git, so rename in DB
WIKI.logger.info(`(STORAGE/GIT) Page marked as renamed: from ${item.oldPath} to ${item.relPath}`) WIKI.logger.info(`(STORAGE/GIT) Page marked as renamed: from ${item.oldPath} to ${item.relPath}`)
@ -195,7 +195,7 @@ module.exports = {
destinationLocale: contentPath.locale, destinationLocale: contentPath.locale,
skipStorage: true skipStorage: true
}) })
} else if (!fileExists && item.deletions > 0 && item.insertions === 0) { } else if (!fileExists && !item.importAll && item.deletions > 0 && item.insertions === 0) {
// Page was deleted by git, can safely mark as deleted in DB // Page was deleted by git, can safely mark as deleted in DB
WIKI.logger.info(`(STORAGE/GIT) Page marked as deleted: ${item.relPath}`) WIKI.logger.info(`(STORAGE/GIT) Page marked as deleted: ${item.relPath}`)
@ -224,7 +224,7 @@ module.exports = {
} else { } else {
// -> Asset // -> Asset
if (fileExists && ((item.before === item.after) || (item.deletions === 0 && item.insertions === 0))) { if (fileExists && !item.importAll && ((item.before === item.after) || (item.deletions === 0 && item.insertions === 0))) {
// Asset was renamed by git, so rename in DB // Asset was renamed by git, so rename in DB
WIKI.logger.info(`(STORAGE/GIT) Asset marked as renamed: from ${item.oldPath} to ${item.relPath}`) WIKI.logger.info(`(STORAGE/GIT) Asset marked as renamed: from ${item.oldPath} to ${item.relPath}`)
@ -240,7 +240,7 @@ module.exports = {
WIKI.logger.info(`(STORAGE/GIT) Asset was not found in the DB, nothing to rename: ${item.relPath}`) WIKI.logger.info(`(STORAGE/GIT) Asset was not found in the DB, nothing to rename: ${item.relPath}`)
} }
continue continue
} else if (!fileExists && ((item.before > 0 && item.after === 0) || (item.deletions > 0 && item.insertions === 0))) { } else if (!fileExists && !item.importAll && ((item.before > 0 && item.after === 0) || (item.deletions > 0 && item.insertions === 0))) {
// Asset was deleted by git, can safely mark as deleted in DB // Asset was deleted by git, can safely mark as deleted in DB
WIKI.logger.info(`(STORAGE/GIT) Asset marked as deleted: ${item.relPath}`) WIKI.logger.info(`(STORAGE/GIT) Asset marked as deleted: ${item.relPath}`)
@ -427,7 +427,8 @@ module.exports = {
relPath, relPath,
file, file,
deletions: 0, deletions: 0,
insertions: 0 insertions: 0,
importAll: true
}], rootUser) }], rootUser)
} }
cb() cb()

@ -288,7 +288,7 @@ module.exports = () => {
// Create root administrator // Create root administrator
WIKI.logger.info('Creating root administrator...') WIKI.logger.info('Creating root administrator...')
const adminUser = await WIKI.models.users.query().insert({ const adminUser = await WIKI.models.users.query().insert({
email: req.body.adminEmail, email: req.body.adminEmail.toLowerCase(),
provider: 'local', provider: 'local',
password: req.body.adminPassword, password: req.body.adminPassword,
name: 'Administrator', name: 'Administrator',

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save