Merge branch 'requarks:main' into main

pull/7707/head
Dylan Hart 1 month ago committed by GitHub
commit d1d78e46e7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -19,7 +19,7 @@ jobs:
packages: write packages: write
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
- name: Set Build Variables - name: Set Build Variables
run: | run: |
@ -42,20 +42,22 @@ jobs:
cat package.json cat package.json
- name: Login to DockerHub - name: Login to DockerHub
uses: docker/login-action@v3 uses: docker/login-action@v4
with: with:
username: ${{ secrets.DOCKERHUB_USERNAME }} username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }} password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Login to GitHub Container Registry - name: Login to GitHub Container Registry
uses: docker/login-action@v3 uses: docker/login-action@v4
with: with:
registry: ghcr.io registry: ghcr.io
username: ${{ github.repository_owner }} username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push Docker images - name: Build and push Docker images
uses: docker/build-push-action@v5 uses: docker/build-push-action@v7
env:
DOCKER_BUILD_SUMMARY: false
with: with:
context: . context: .
file: dev/build/Dockerfile file: dev/build/Dockerfile
@ -77,7 +79,7 @@ jobs:
find _dist/wiki/ -printf "%P\n" | tar -czf wiki-js.tar.gz --no-recursion -C _dist/wiki/ -T - find _dist/wiki/ -printf "%P\n" | tar -czf wiki-js.tar.gz --no-recursion -C _dist/wiki/ -T -
- name: Upload a Build Artifact - name: Upload a Build Artifact
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v7
with: with:
name: drop name: drop
path: wiki-js.tar.gz path: wiki-js.tar.gz
@ -92,7 +94,7 @@ jobs:
dbtype: [postgres, mysql, mariadb, sqlite] dbtype: [postgres, mysql, mariadb, sqlite]
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
- name: Set Test Variables - name: Set Test Variables
run: | run: |
@ -115,21 +117,13 @@ jobs:
arm: arm:
name: ARM Build name: ARM Build
runs-on: ubuntu-latest runs-on: ubuntu-24.04-arm
needs: [cypress] needs: [cypress]
permissions: permissions:
packages: write packages: write
strategy:
matrix:
include:
- platform: linux/arm64
docker: arm64
# - platform: linux/arm/v7
# docker: armv7
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
- name: Set Version Variables - name: Set Version Variables
run: | run: |
@ -142,26 +136,26 @@ jobs:
fi fi
- name: Set up QEMU - name: Set up QEMU
uses: docker/setup-qemu-action@v3 uses: docker/setup-qemu-action@v4
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3 uses: docker/setup-buildx-action@v4
- name: Login to DockerHub - name: Login to DockerHub
uses: docker/login-action@v3 uses: docker/login-action@v4
with: with:
username: ${{ secrets.DOCKERHUB_USERNAME }} username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }} password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Login to GitHub Container Registry - name: Login to GitHub Container Registry
uses: docker/login-action@v3 uses: docker/login-action@v4
with: with:
registry: ghcr.io registry: ghcr.io
username: ${{ github.repository_owner }} username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
- name: Download a Build Artifact - name: Download a Build Artifact
uses: actions/download-artifact@v4 uses: actions/download-artifact@v8
with: with:
name: drop name: drop
path: drop path: drop
@ -172,16 +166,18 @@ jobs:
tar -xzf $GITHUB_WORKSPACE/drop/wiki-js.tar.gz -C $GITHUB_WORKSPACE/build --exclude=node_modules tar -xzf $GITHUB_WORKSPACE/drop/wiki-js.tar.gz -C $GITHUB_WORKSPACE/build --exclude=node_modules
- name: Build and push Docker images - name: Build and push Docker images
uses: docker/build-push-action@v5 uses: docker/build-push-action@v7
env:
DOCKER_BUILD_SUMMARY: false
with: with:
context: . context: .
file: dev/build-arm/Dockerfile file: dev/build-arm/Dockerfile
platforms: ${{ matrix.platform }} platforms: linux/arm64
provenance: false provenance: false
push: true push: true
tags: | tags: |
requarks/wiki:canary-${{ matrix.docker }}-${{ env.REL_VERSION_STRICT }} requarks/wiki:canary-arm64-${{ env.REL_VERSION_STRICT }}
ghcr.io/requarks/wiki:canary-${{ matrix.docker }}-${{ env.REL_VERSION_STRICT }} ghcr.io/requarks/wiki:canary-arm64-${{ env.REL_VERSION_STRICT }}
windows: windows:
name: Windows Build name: Windows Build
@ -190,12 +186,12 @@ jobs:
steps: steps:
- name: Setup Node.js environment - name: Setup Node.js environment
uses: actions/setup-node@v4 uses: actions/setup-node@v6
with: with:
node-version: 20.x node-version: 24.x
- name: Download a Build Artifact - name: Download a Build Artifact
uses: actions/download-artifact@v4 uses: actions/download-artifact@v8
with: with:
name: drop name: drop
path: drop path: drop
@ -218,10 +214,10 @@ jobs:
Copy-Item patch-extractfile.json win\node_modules\extract-files\package.json -Force Copy-Item patch-extractfile.json win\node_modules\extract-files\package.json -Force
- name: Create Bundle - name: Create Bundle
run: tar -czf wiki-js-windows.tar.gz -C $env:GITHUB_WORKSPACE\win . run: tar -czf $env:GITHUB_WORKSPACE\wiki-js-windows.tar.gz -C $env:GITHUB_WORKSPACE\win .
- name: Upload a Build Artifact - name: Upload a Build Artifact
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v7
with: with:
name: drop-win name: drop-win
path: wiki-js-windows.tar.gz path: wiki-js-windows.tar.gz
@ -241,13 +237,13 @@ jobs:
echo "REL_VERSION_STRICT=${GITHUB_REF_NAME#?}" >> $GITHUB_ENV echo "REL_VERSION_STRICT=${GITHUB_REF_NAME#?}" >> $GITHUB_ENV
- name: Login to DockerHub - name: Login to DockerHub
uses: docker/login-action@v3 uses: docker/login-action@v4
with: with:
username: ${{ secrets.DOCKERHUB_USERNAME }} username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }} password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Login to GitHub Container Registry - name: Login to GitHub Container Registry
uses: docker/login-action@v3 uses: docker/login-action@v4
with: with:
registry: ghcr.io registry: ghcr.io
username: ${{ github.repository_owner }} username: ${{ github.repository_owner }}
@ -282,13 +278,13 @@ jobs:
echo "REL_VERSION_STRICT=${GITHUB_REF_NAME#?}" >> $GITHUB_ENV echo "REL_VERSION_STRICT=${GITHUB_REF_NAME#?}" >> $GITHUB_ENV
- name: Login to DockerHub - name: Login to DockerHub
uses: docker/login-action@v3 uses: docker/login-action@v4
with: with:
username: ${{ secrets.DOCKERHUB_USERNAME }} username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }} password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Login to GitHub Container Registry - name: Login to GitHub Container Registry
uses: docker/login-action@v3 uses: docker/login-action@v4
with: with:
registry: ghcr.io registry: ghcr.io
username: ${{ github.repository_owner }} username: ${{ github.repository_owner }}
@ -328,13 +324,13 @@ jobs:
docker manifest push -p ghcr.io/requarks/wiki:latest docker manifest push -p ghcr.io/requarks/wiki:latest
- name: Download Linux Build - name: Download Linux Build
uses: actions/download-artifact@v4 uses: actions/download-artifact@v8
with: with:
name: drop name: drop
path: drop path: drop
- name: Download Windows Build - name: Download Windows Build
uses: actions/download-artifact@v4 uses: actions/download-artifact@v8
with: with:
name: drop-win name: drop-win
path: drop-win path: drop-win
@ -348,7 +344,7 @@ jobs:
writeToFile: false writeToFile: false
- name: Update GitHub Release - name: Update GitHub Release
uses: ncipollo/release-action@v1.12.0 uses: ncipollo/release-action@v1.21.0
with: with:
allowUpdates: true allowUpdates: true
draft: false draft: false
@ -370,7 +366,7 @@ jobs:
# SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK # SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK
- name: Notify Telegram Channel - name: Notify Telegram Channel
uses: appleboy/telegram-action@v0.1.1 uses: appleboy/telegram-action@v1.0.1
with: with:
to: ${{ secrets.TELEGRAM_TO }} to: ${{ secrets.TELEGRAM_TO }}
token: ${{ secrets.TELEGRAM_TOKEN }} token: ${{ secrets.TELEGRAM_TOKEN }}
@ -392,7 +388,7 @@ jobs:
# needs: [release] # needs: [release]
# steps: # steps:
# - uses: actions/checkout@v4 # - uses: actions/checkout@v6
# - name: Set Version Variables # - name: Set Version Variables
# run: | # run: |

@ -16,11 +16,12 @@ jobs:
steps: steps:
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
- uses: actions/checkout@v2 - uses: actions/checkout@v6
- name: Package and Push Chart - name: Package and Push Chart
run: | run: |
export CHARTVER=$(yq '.version' dev/helm/Chart.yaml)
helm plugin install https://github.com/chartmuseum/helm-push.git helm plugin install https://github.com/chartmuseum/helm-push.git
helm repo add chartmuseum https://charts.js.wiki 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 cm-push --version="$CHARTVER" --username="${{secrets.HELM_REPO_USERNAME}}" --password="${{secrets.HELM_REPO_PASSWORD}}" dev/helm/ chartmuseum
helm repo remove chartmuseum helm repo remove chartmuseum

@ -14,13 +14,13 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v6
- name: Install Packer - name: Install Packer
run: | run: |
curl -fsSL https://apt.releases.hashicorp.com/gpg | sudo apt-key add - wget -O - https://apt.releases.hashicorp.com/gpg | sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg
sudo apt-add-repository "deb [arch=amd64] https://apt.releases.hashicorp.com $(lsb_release -cs) main" echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(grep -oP '(?<=UBUNTU_CODENAME=).*' /etc/os-release || lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list
sudo apt-get update && sudo apt-get install packer sudo apt update && sudo apt install packer
- name: Build Droplet Image - name: Build Droplet Image
env: env:
@ -28,4 +28,5 @@ jobs:
WIKI_APP_VERSION: ${{ github.event.inputs.version }} WIKI_APP_VERSION: ${{ github.event.inputs.version }}
working-directory: dev/packer working-directory: dev/packer
run: | run: |
packer plugins install github.com/digitalocean/digitalocean
packer build digitalocean.json packer build digitalocean.json

@ -1 +1 @@
v18.17.1 v24.12.0

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

@ -31,7 +31,6 @@
- [Feature Requests](https://feedback.js.wiki/wiki) - [Feature Requests](https://feedback.js.wiki/wiki)
- Chat with us on [Discord](https://discord.gg/rcxt9QS2jd) - Chat with us on [Discord](https://discord.gg/rcxt9QS2jd)
- [Translations](https://docs.requarks.io/dev/translations) *(We need your help!)* - [Translations](https://docs.requarks.io/dev/translations) *(We need your help!)*
- [E2E Testing Results](https://dashboard.cypress.io/projects/r7qxah/runs)
- [Special Thanks](#special-thanks) - [Special Thanks](#special-thanks)
- [Contribute](#contributors) - [Contribute](#contributors)
@ -91,68 +90,74 @@ Support this project by becoming a sponsor. Your name will show up in the Contri
<table> <table>
<tbody> <tbody>
<tr> <tr>
<td align="center" valign="middle" width="148"> <td align="center" valign="middle" width="130">
<a href="https://acceleanation.com/" target="_blank"> <a href="https://acceleanation.com/" target="_blank">
<img src="https://avatars.githubusercontent.com/u/41210718?s=200&v=4"> <img src="https://avatars.githubusercontent.com/u/41210718?s=200&v=4">
</a> </a>
</td> </td>
<td align="center" valign="middle" width="148"> <td align="center" valign="middle" width="130">
<a href="https://github.com/alexksso" target="_blank"> <a href="https://github.com/alexksso" target="_blank">
Alexander Casassovici<br />(@alexksso) Alexander Casassovici<br />(@alexksso)
</a> </a>
</td> </td>
<td align="center" valign="middle" width="148"> <td align="center" valign="middle" width="130">
<a href="https://github.com/broxen" target="_blank"> <a href="https://github.com/broxen" target="_blank">
Broxen<br />(@broxen) Broxen<br />(@broxen)
</a> </a>
</td> </td>
<td align="center" valign="middle" width="148"> <td align="center" valign="middle" width="130">
<a href="https://github.com/xDacon" target="_blank"> <a href="https://github.com/xDacon" target="_blank">
Dacon<br />(@xDacon) Dacon<br />(@xDacon)
</a> </a>
</td> </td>
<td align="center" valign="middle" width="148"> <td align="center" valign="middle" width="130">
<a href="https://github.com/DonNabla" target="_blank">
Maxime Pierre<br />(@DonNabla)
</a>
</td>
<td align="center" valign="middle" width="130">
<a href="https://github.com/GigabiteLabs" target="_blank"> <a href="https://github.com/GigabiteLabs" target="_blank">
<img src="https://static.requarks.io/sponsors/gigabitelabs-148x129.png"> <img src="https://static.requarks.io/sponsors/gigabitelabs-148x129.png">
</a> </a>
</td> </td>
<td align="center" valign="middle" width="148"> <td align="center" valign="middle" width="130">
<a href="https://www.hostwiki.com/" target="_blank"> <a href="https://www.hostwiki.com/" target="_blank">
<img src="https://cdn.js.wiki/images/sponsors/hostwiki.png"> <img src="https://cdn.js.wiki/images/sponsors/hostwiki.png">
</a> </a>
</td> </td>
</tr> </tr>
<tr> <tr>
<td align="center" valign="middle" width="148"> <td align="center" valign="middle" width="130">
<a href="https://github.com/JayDaley" target="_blank"> <a href="https://github.com/JayDaley" target="_blank">
Jay Daley<br />(@JayDaley) Jay Daley<br />(@JayDaley)
</a> </a>
</td> </td>
<td align="center" valign="middle" width="148"> <td align="center" valign="middle" width="130">
<a href="https://github.com/idokka" target="_blank"> <a href="https://github.com/idokka" target="_blank">
Oleksii<br />(@idokka) Oleksii<br />(@idokka)
</a> </a>
</td> </td>
<td align="center" valign="middle" width="148"> <td align="center" valign="middle" width="130">
<a href="https://www.openhost-network.com/" target="_blank"> <a href="https://www.openhost-network.com/" target="_blank">
<img src="https://avatars.githubusercontent.com/u/114218287?s=200&v=4"> <img src="https://avatars.githubusercontent.com/u/114218287?s=200&v=4">
</a> </a>
</td> </td>
<td align="center" valign="middle" width="148"> <td align="center" valign="middle" width="130">
<a href="https://www.prevo.ch/" target="_blank"> <a href="https://www.prevo.ch/" target="_blank">
<img src="https://avatars.githubusercontent.com/u/114394792?v=4"> <img src="https://avatars.githubusercontent.com/u/114394792?v=4">
</a> </a>
</td> </td>
<td align="center" valign="middle" width="148"> <td align="center" valign="middle" width="130">
<a href="http://www.taicep.org/" target="_blank"> <a href="https://github.com/shanekearney" target="_blank">
<img src="https://avatars.githubusercontent.com/u/160072306?v=4"> Shane Kearney<br />(@shanekearney)
</a> </a>
</td> </td>
<td align="center" valign="middle" colspan="1"> <td align="center" valign="middle" width="130">
<a href="https://github.com/sponsors/NGPixel" target="_blank"> <a href="http://www.taicep.org/" target="_blank">
<img src="https://static.requarks.io/sponsors/become-148x72.png"> <img src="https://avatars.githubusercontent.com/u/160072306?v=4">
</a> </a>
</td> </td>
<td align="center" valign="middle" width="130"></td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
@ -506,9 +511,6 @@ This project exists thanks to all the people who contribute. [[Contribute]](http
![Icons8](https://static.requarks.io/logo/icons8-text-h40.png) ![Icons8](https://static.requarks.io/logo/icons8-text-h40.png)
[Icons8](https://icons8.com/) for providing access to their beautiful icon sets. [Icons8](https://icons8.com/) for providing access to their beautiful icon sets.
![Localazy](https://static.requarks.io/logo/localazy-h40.png)
[Localazy](https://localazy.com/) for providing access to their great localization service.
![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.

@ -103,7 +103,7 @@ const graphQLLink = ApolloLink.from([
// Handle renewed JWT // Handle renewed JWT
const newJWT = resp.headers.get('new-jwt') const newJWT = resp.headers.get('new-jwt')
if (newJWT) { if (newJWT) {
Cookies.set('jwt', newJWT, { expires: 365 }) Cookies.set('jwt', newJWT, { expires: 365, secure: window.location.protocol === 'https:' })
} }
return resp return resp
} }
@ -114,7 +114,11 @@ const graphQLWSLink = new WebSocketLink({
uri: graphQLWSEndpoint, uri: graphQLWSEndpoint,
options: { options: {
reconnect: true, reconnect: true,
lazy: true lazy: true,
connectionParams: () => {
const token = Cookies.get('jwt')
return token ? { token } : {}
}
} }
}) })
@ -148,30 +152,30 @@ Vue.prototype.Velocity = Velocity
// Register Vue Components // Register Vue Components
// ==================================== // ====================================
Vue.component('admin', () => import(/* webpackChunkName: "admin" */ './components/admin.vue')) Vue.component('Admin', () => import(/* webpackChunkName: "admin" */ './components/admin.vue'))
Vue.component('comments', () => import(/* webpackChunkName: "comments" */ './components/comments.vue')) Vue.component('Comments', () => import(/* webpackChunkName: "comments" */ './components/comments.vue'))
Vue.component('editor', () => import(/* webpackPrefetch: -100, webpackChunkName: "editor" */ './components/editor.vue')) Vue.component('Editor', () => import(/* webpackPrefetch: -100, webpackChunkName: "editor" */ './components/editor.vue'))
Vue.component('history', () => import(/* webpackChunkName: "history" */ './components/history.vue')) Vue.component('History', () => import(/* webpackChunkName: "history" */ './components/history.vue'))
Vue.component('loader', () => import(/* webpackPrefetch: true, webpackChunkName: "ui-extra" */ './components/common/loader.vue')) Vue.component('Loader', () => import(/* webpackPrefetch: true, webpackChunkName: "ui-extra" */ './components/common/loader.vue'))
Vue.component('login', () => import(/* webpackPrefetch: true, webpackChunkName: "login" */ './components/login.vue')) Vue.component('Login', () => import(/* webpackPrefetch: true, webpackChunkName: "login" */ './components/login.vue'))
Vue.component('nav-header', () => import(/* webpackMode: "eager" */ './components/common/nav-header.vue')) Vue.component('NavHeader', () => import(/* webpackMode: "eager" */ './components/common/nav-header.vue'))
Vue.component('new-page', () => import(/* webpackChunkName: "new-page" */ './components/new-page.vue')) Vue.component('NewPage', () => import(/* webpackChunkName: "new-page" */ './components/new-page.vue'))
Vue.component('notify', () => import(/* webpackMode: "eager" */ './components/common/notify.vue')) Vue.component('Notify', () => import(/* webpackMode: "eager" */ './components/common/notify.vue'))
Vue.component('not-found', () => import(/* webpackChunkName: "not-found" */ './components/not-found.vue')) Vue.component('NotFound', () => import(/* webpackChunkName: "not-found" */ './components/not-found.vue'))
Vue.component('page-selector', () => import(/* webpackPrefetch: true, webpackChunkName: "ui-extra" */ './components/common/page-selector.vue')) Vue.component('PageSelector', () => import(/* webpackPrefetch: true, webpackChunkName: "ui-extra" */ './components/common/page-selector.vue'))
Vue.component('page-source', () => import(/* webpackChunkName: "source" */ './components/source.vue')) Vue.component('PageSource', () => import(/* webpackChunkName: "source" */ './components/source.vue'))
Vue.component('profile', () => import(/* webpackChunkName: "profile" */ './components/profile.vue')) Vue.component('Profile', () => import(/* webpackChunkName: "profile" */ './components/profile.vue'))
Vue.component('register', () => import(/* webpackChunkName: "register" */ './components/register.vue')) Vue.component('Register', () => import(/* webpackChunkName: "register" */ './components/register.vue'))
Vue.component('search-results', () => import(/* webpackPrefetch: true, webpackChunkName: "ui-extra" */ './components/common/search-results.vue')) Vue.component('SearchResults', () => import(/* webpackPrefetch: true, webpackChunkName: "ui-extra" */ './components/common/search-results.vue'))
Vue.component('social-sharing', () => import(/* webpackPrefetch: true, webpackChunkName: "ui-extra" */ './components/common/social-sharing.vue')) Vue.component('SocialSharing', () => import(/* webpackPrefetch: true, webpackChunkName: "ui-extra" */ './components/common/social-sharing.vue'))
Vue.component('tags', () => import(/* webpackChunkName: "tags" */ './components/tags.vue')) Vue.component('Tags', () => import(/* webpackChunkName: "tags" */ './components/tags.vue'))
Vue.component('unauthorized', () => import(/* webpackChunkName: "unauthorized" */ './components/unauthorized.vue')) Vue.component('Unauthorized', () => import(/* webpackChunkName: "unauthorized" */ './components/unauthorized.vue'))
Vue.component('v-card-chin', () => import(/* webpackPrefetch: true, webpackChunkName: "ui-extra" */ './components/common/v-card-chin.vue')) Vue.component('VCardChin', () => import(/* webpackPrefetch: true, webpackChunkName: "ui-extra" */ './components/common/v-card-chin.vue'))
Vue.component('v-card-info', () => import(/* webpackPrefetch: true, webpackChunkName: "ui-extra" */ './components/common/v-card-info.vue')) Vue.component('VCardInfo', () => import(/* webpackPrefetch: true, webpackChunkName: "ui-extra" */ './components/common/v-card-info.vue'))
Vue.component('welcome', () => import(/* webpackChunkName: "welcome" */ './components/welcome.vue')) Vue.component('Welcome', () => import(/* webpackChunkName: "welcome" */ './components/welcome.vue'))
Vue.component('nav-footer', () => import(/* webpackChunkName: "theme" */ './themes/' + siteConfig.theme + '/components/nav-footer.vue')) Vue.component('NavFooter', () => import(/* webpackChunkName: "theme" */ './themes/' + siteConfig.theme + '/components/nav-footer.vue'))
Vue.component('page', () => import(/* webpackChunkName: "theme" */ './themes/' + siteConfig.theme + '/components/page.vue')) Vue.component('Page', () => import(/* webpackChunkName: "theme" */ './themes/' + siteConfig.theme + '/components/page.vue'))
let bootstrap = () => { let bootstrap = () => {
// ==================================== // ====================================

@ -149,28 +149,28 @@ export default {
items: [ items: [
{ {
permission: 'write:users', permission: 'write:users',
hint: 'Can create or authorize new users, but not modify existing ones', hint: 'Can create or authorize new users, but not modify existing ones. Can only assign to non-administrative groups',
warning: false, warning: false,
restrictedForSystem: true, restrictedForSystem: true,
disabled: false disabled: false
}, },
{ {
permission: 'manage:users', permission: 'manage:users',
hint: 'Can manage all users (but not users with administrative permissions)', hint: 'Can create, authorize and modify ANY users. Can only assign to non-administrative groups',
warning: false, warning: false,
restrictedForSystem: true, restrictedForSystem: true,
disabled: false disabled: false
}, },
{ {
permission: 'write:groups', permission: 'write:groups',
hint: 'Can manage groups and assign CONTENT permissions / page rules', hint: 'Can manage groups and set CONTENT permissions / page rules. Can only assign users to non-administrative groups',
warning: false, warning: false,
restrictedForSystem: true, restrictedForSystem: true,
disabled: false disabled: false
}, },
{ {
permission: 'manage:groups', permission: 'manage:groups',
hint: 'Can manage groups and assign ANY permissions (but not manage:system) / page rules', hint: 'Can manage groups and set ANY permissions (but not manage:system) / page rules. Can assign users to ANY groups (except groups with the manage:system permission)',
warning: true, warning: true,
restrictedForSystem: true, restrictedForSystem: true,
disabled: false disabled: false
@ -203,7 +203,7 @@ export default {
}, },
{ {
permission: 'manage:system', permission: 'manage:system',
hint: 'Can manage and access everything. Root administrator.', hint: 'Can manage and access everything. Root administrator',
warning: true, warning: true,
restrictedForSystem: true, restrictedForSystem: true,
disabled: true disabled: true

@ -39,14 +39,14 @@
v-list-item-icon v-list-item-icon
v-icon(color='indigo') mdi-pencil v-icon(color='indigo') mdi-pencil
v-list-item-title Edit v-list-item-title Edit
v-list-item(@click='', disabled) //- v-list-item(@click='', disabled)
v-list-item-icon //- v-list-item-icon
v-icon(color='grey') mdi-cube-scan //- v-icon(color='grey') mdi-cube-scan
v-list-item-title Re-Render //- v-list-item-title Re-Render
v-list-item(@click='', disabled) //- v-list-item(@click='', disabled)
v-list-item-icon //- v-list-item-icon
v-icon(color='grey') mdi-earth-remove //- v-icon(color='grey') mdi-earth-remove
v-list-item-title Unpublish //- v-list-item-title Unpublish
v-list-item(:href='`/s/` + page.locale + `/` + page.path') v-list-item(:href='`/s/` + page.locale + `/` + page.path')
v-list-item-icon v-list-item-icon
v-icon(color='indigo') mdi-code-tags v-icon(color='indigo') mdi-code-tags
@ -55,14 +55,14 @@
v-list-item-icon v-list-item-icon
v-icon(color='indigo') mdi-history v-icon(color='indigo') mdi-history
v-list-item-title View History v-list-item-title View History
v-list-item(@click='', disabled) //- v-list-item(@click='', disabled)
v-list-item-icon //- v-list-item-icon
v-icon(color='grey') mdi-content-duplicate //- v-icon(color='grey') mdi-content-duplicate
v-list-item-title Duplicate //- v-list-item-title Duplicate
v-list-item(@click='', disabled) //- v-list-item(@click='', disabled)
v-list-item-icon //- v-list-item-icon
v-icon(color='grey') mdi-content-save-move-outline //- v-icon(color='grey') mdi-content-save-move-outline
v-list-item-title Move / Rename //- v-list-item-title Move / Rename
v-dialog(v-model='deletePageDialog', max-width='500') v-dialog(v-model='deletePageDialog', max-width='500')
template(v-slot:activator='{ on }') template(v-slot:activator='{ on }')
v-list-item(v-on='on') v-list-item(v-on='on')

@ -10,9 +10,9 @@
v-spacer v-spacer
v-btn.animated.fadeInDown.wait-p1s(icon, color='grey', outlined, @click='refresh') v-btn.animated.fadeInDown.wait-p1s(icon, color='grey', outlined, @click='refresh')
v-icon.grey--text mdi-refresh v-icon.grey--text mdi-refresh
v-btn.animated.fadeInDown.mx-3(color='primary', outlined, @click='recyclebin', disabled) //- v-btn.animated.fadeInDown.mx-3(color='primary', outlined, @click='recyclebin', disabled)
v-icon(left) mdi-delete-outline //- v-icon(left) mdi-delete-outline
span Recycle Bin //- span Recycle Bin
v-btn.animated.fadeInDown(color='primary', depressed, large, to='pages/visualize') v-btn.animated.fadeInDown(color='primary', depressed, large, to='pages/visualize')
v-icon(left) mdi-graph v-icon(left) mdi-graph
span Visualize span Visualize

@ -70,13 +70,13 @@
v-model='mustChangePwd' v-model='mustChangePwd'
hide-details hide-details
) )
v-checkbox( //- v-checkbox(
color='primary' //- color='primary'
label='Send a welcome email' //- label='Send a welcome email'
hide-details //- hide-details
v-model='sendWelcomeEmail' //- v-model='sendWelcomeEmail'
disabled //- disabled
) //- )
v-card-chin v-card-chin
v-spacer v-spacer
v-btn(text, @click='isShown = false') Cancel v-btn(text, @click='isShown = false') Cancel

@ -337,12 +337,12 @@
.caption.grey--text.mt-3 {{$t('profile:activity.lastLoginOn')}} .caption.grey--text.mt-3 {{$t('profile:activity.lastLoginOn')}}
.body-2: strong {{ user.lastLoginAt | moment('LLLL') }} .body-2: strong {{ user.lastLoginAt | moment('LLLL') }}
v-card.mt-3.animated.fadeInUp.wait-p6s //- v-card.mt-3.animated.fadeInUp.wait-p6s
v-toolbar(color='teal', dense, dark, flat) //- v-toolbar(color='teal', dense, dark, flat)
v-icon.mr-2 mdi-file-document-box-multiple-outline //- v-icon.mr-2 mdi-file-document-box-multiple-outline
span Content //- span Content
v-card-text //- v-card-text
em.caption.grey--text Coming soon //- em.caption.grey--text Coming soon
v-dialog(v-model='deleteUserDialog', max-width='500') v-dialog(v-model='deleteUserDialog', max-width='500')
v-card v-card

@ -15,7 +15,7 @@
prepend-inner-icon='mdi-magnify' prepend-inner-icon='mdi-magnify'
:loading='searchIsLoading' :loading='searchIsLoading'
@keyup.enter='searchEnter' @keyup.enter='searchEnter'
autocomplete='none' autocomplete='off'
) )
v-layout(row) v-layout(row)
v-flex(xs5, md4) v-flex(xs5, md4)
@ -68,7 +68,7 @@
@blur='searchBlur' @blur='searchBlur'
@keyup.down='searchMove(`down`)' @keyup.down='searchMove(`down`)'
@keyup.up='searchMove(`up`)' @keyup.up='searchMove(`up`)'
autocomplete='none' autocomplete='off'
) )
v-tooltip(bottom) v-tooltip(bottom)
template(v-slot:activator='{ on }') template(v-slot:activator='{ on }')
@ -476,7 +476,11 @@ export default {
window.location.assign('/logout') window.location.assign('/logout')
}, },
goHome () { goHome () {
window.location.assign('/') if (this.locales && this.locales.length > 0) {
window.location.assign(`/${this.locale}/home`)
} else {
window.location.assign('/')
}
} }
} }
} }

@ -228,7 +228,8 @@ export default {
}) })
this.previewHTML = DOMPurify.sanitize($.html(), { this.previewHTML = DOMPurify.sanitize($.html(), {
ADD_TAGS: ['foreignObject'] ADD_TAGS: ['foreignObject'],
HTML_INTEGRATION_POINTS: { foreignobject: true }
}) })
}, },
/** /**

@ -454,7 +454,8 @@ export default {
// this.$store.set('editor/content', newContent) // this.$store.set('editor/content', newContent)
this.processMarkers(this.cm.firstLine(), this.cm.lastLine()) this.processMarkers(this.cm.firstLine(), this.cm.lastLine())
this.previewHTML = DOMPurify.sanitize(md.render(newContent), { this.previewHTML = DOMPurify.sanitize(md.render(newContent), {
ADD_TAGS: ['foreignObject'] ADD_TAGS: ['foreignObject'],
HTML_INTEGRATION_POINTS: { foreignobject: true }
}) })
this.$nextTick(() => { this.$nextTick(() => {
tabsetHelper.format() tabsetHelper.format()

@ -83,31 +83,31 @@
v-btn(icon, v-on='on', tile, small, @click.left='currentFileId = props.item.id') v-btn(icon, v-on='on', tile, small, @click.left='currentFileId = props.item.id')
v-icon(color='grey darken-2') mdi-dots-horizontal v-icon(color='grey darken-2') mdi-dots-horizontal
v-list(nav, style='border-top: 5px solid #444;') v-list(nav, style='border-top: 5px solid #444;')
v-list-item(@click='', disabled) //- v-list-item(@click='', disabled)
v-list-item-avatar(size='24') //- v-list-item-avatar(size='24')
v-icon(color='teal') mdi-text-short //- v-icon(color='teal') mdi-text-short
v-list-item-content {{$t('common:actions.properties')}} //- v-list-item-content {{$t('common:actions.properties')}}
template(v-if='props.item.kind === `IMAGE`') //- template(v-if='props.item.kind === `IMAGE`')
v-list-item(@click='previewDialog = true', disabled) //- v-list-item(@click='previewDialog = true', disabled)
v-list-item-avatar(size='24') //- v-list-item-avatar(size='24')
v-icon(color='green') mdi-image-search-outline //- v-icon(color='green') mdi-image-search-outline
v-list-item-content {{$t('common:actions.preview')}} //- v-list-item-content {{$t('common:actions.preview')}}
v-list-item(@click='', disabled) //- v-list-item(@click='', disabled)
v-list-item-avatar(size='24') //- v-list-item-avatar(size='24')
v-icon(color='indigo') mdi-crop-rotate //- v-icon(color='indigo') mdi-crop-rotate
v-list-item-content {{$t('common:actions.edit')}} //- v-list-item-content {{$t('common:actions.edit')}}
v-list-item(@click='', disabled) //- v-list-item(@click='', disabled)
v-list-item-avatar(size='24') //- v-list-item-avatar(size='24')
v-icon(color='purple') mdi-flash-circle //- v-icon(color='purple') mdi-flash-circle
v-list-item-content {{$t('common:actions.optimize')}} //- v-list-item-content {{$t('common:actions.optimize')}}
v-list-item(@click='openRenameDialog') v-list-item(@click='openRenameDialog')
v-list-item-avatar(size='24') v-list-item-avatar(size='24')
v-icon(color='orange') mdi-keyboard-outline v-icon(color='orange') mdi-keyboard-outline
v-list-item-content {{$t('common:actions.rename')}} v-list-item-content {{$t('common:actions.rename')}}
v-list-item(@click='', disabled) //- v-list-item(@click='', disabled)
v-list-item-avatar(size='24') //- v-list-item-avatar(size='24')
v-icon(color='blue') mdi-file-move //- v-icon(color='blue') mdi-file-move
v-list-item-content {{$t('common:actions.move')}} //- v-list-item-content {{$t('common:actions.move')}}
v-list-item(@click='deleteDialog = true') v-list-item(@click='deleteDialog = true')
v-list-item-avatar(size='24') v-list-item-avatar(size='24')
v-icon(color='red') mdi-file-hidden v-icon(color='red') mdi-file-hidden
@ -154,25 +154,25 @@
v-spacer v-spacer
v-btn.px-4(color='teal', dark, @click='upload') {{$t('common:actions.upload')}} v-btn.px-4(color='teal', dark, @click='upload') {{$t('common:actions.upload')}}
v-card.mt-3.radius-7.animated.fadeInRight.wait-p4s(:light='!$vuetify.theme.dark', :dark='$vuetify.theme.dark') //- v-card.mt-3.radius-7.animated.fadeInRight.wait-p4s(:light='!$vuetify.theme.dark', :dark='$vuetify.theme.dark')
v-card-text.pb-0 //- v-card-text.pb-0
v-toolbar.radius-7(:color='$vuetify.theme.dark ? `teal` : `teal lighten-5`', dense, flat) //- v-toolbar.radius-7(:color='$vuetify.theme.dark ? `teal` : `teal lighten-5`', dense, flat)
v-icon.mr-3(:color='$vuetify.theme.dark ? `white` : `teal`') mdi-cloud-download //- v-icon.mr-3(:color='$vuetify.theme.dark ? `white` : `teal`') mdi-cloud-download
.body-2(:class='$vuetify.theme.dark ? `white--text` : `teal--text`') {{$t('editor:assets.fetchImage')}} //- .body-2(:class='$vuetify.theme.dark ? `white--text` : `teal--text`') {{$t('editor:assets.fetchImage')}}
v-spacer //- v-spacer
v-chip(label, color='white', small).teal--text coming soon //- v-chip(label, color='white', small).teal--text coming soon
v-text-field.mt-3( //- v-text-field.mt-3(
v-model='remoteImageUrl' //- v-model='remoteImageUrl'
outlined //- outlined
color='teal' //- color='teal'
single-line //- single-line
placeholder='https://example.com/image.jpg' //- placeholder='https://example.com/image.jpg'
) //- )
v-divider //- v-divider
v-card-actions.pa-3 //- v-card-actions.pa-3
.caption.grey--text.text-darken-2 Max 5 MB //- .caption.grey--text.text-darken-2 Max 5 MB
v-spacer //- v-spacer
v-btn.px-4(color='teal', disabled) {{$t('common:actions.fetch')}} //- v-btn.px-4(color='teal', disabled) {{$t('common:actions.fetch')}}
v-card.mt-3.radius-7.animated.fadeInRight.wait-p4s(:light='!$vuetify.theme.dark', :dark='$vuetify.theme.dark') v-card.mt-3.radius-7.animated.fadeInRight.wait-p4s(:light='!$vuetify.theme.dark', :dark='$vuetify.theme.dark')
v-card-text.pb-0 v-card-text.pb-0

@ -21,7 +21,7 @@
v-tab {{$t('editor:props.info')}} v-tab {{$t('editor:props.info')}}
v-tab {{$t('editor:props.scheduling')}} v-tab {{$t('editor:props.scheduling')}}
v-tab(:disabled='!hasScriptPermission') {{$t('editor:props.scripts')}} v-tab(:disabled='!hasScriptPermission') {{$t('editor:props.scripts')}}
v-tab(disabled) {{$t('editor:props.social')}} //- v-tab(disabled) {{$t('editor:props.social')}}
v-tab(:disabled='!hasStylePermission') {{$t('editor:props.styles')}} v-tab(:disabled='!hasStylePermission') {{$t('editor:props.styles')}}
v-tab-item(transition='fade-transition', reverse-transition='fade-transition') v-tab-item(transition='fade-transition', reverse-transition='fade-transition')
v-card-text.pt-5 v-card-text.pt-5
@ -196,42 +196,42 @@
.editor-props-codeeditor-hint .editor-props-codeeditor-hint
.caption {{$t('editor:props.htmlHint')}} .caption {{$t('editor:props.htmlHint')}}
v-tab-item(transition='fade-transition', reverse-transition='fade-transition') //- v-tab-item(transition='fade-transition', reverse-transition='fade-transition')
v-card-text //- v-card-text
.overline {{$t('editor:props.socialFeatures')}} //- .overline {{$t('editor:props.socialFeatures')}}
v-switch( //- v-switch(
:label='$t(`editor:props.allowComments`)' //- :label='$t(`editor:props.allowComments`)'
v-model='isPublished' //- v-model='isPublished'
color='primary' //- color='primary'
:hint='$t(`editor:props.allowCommentsHint`)' //- :hint='$t(`editor:props.allowCommentsHint`)'
persistent-hint //- persistent-hint
inset //- inset
) //- )
v-switch( //- v-switch(
:label='$t(`editor:props.allowRatings`)' //- :label='$t(`editor:props.allowRatings`)'
v-model='isPublished' //- v-model='isPublished'
color='primary' //- color='primary'
:hint='$t(`editor:props.allowRatingsHint`)' //- :hint='$t(`editor:props.allowRatingsHint`)'
persistent-hint //- persistent-hint
disabled //- disabled
inset //- inset
) //- )
v-switch( //- v-switch(
:label='$t(`editor:props.displayAuthor`)' //- :label='$t(`editor:props.displayAuthor`)'
v-model='isPublished' //- v-model='isPublished'
color='primary' //- color='primary'
:hint='$t(`editor:props.displayAuthorHint`)' //- :hint='$t(`editor:props.displayAuthorHint`)'
persistent-hint //- persistent-hint
inset //- inset
) //- )
v-switch( //- v-switch(
:label='$t(`editor:props.displaySharingBar`)' //- :label='$t(`editor:props.displaySharingBar`)'
v-model='isPublished' //- v-model='isPublished'
color='primary' //- color='primary'
:hint='$t(`editor:props.displaySharingBarHint`)' //- :hint='$t(`editor:props.displaySharingBarHint`)'
persistent-hint //- persistent-hint
inset //- inset
) //- )
v-tab-item(:transition='false', :reverse-transition='false') v-tab-item(:transition='false', :reverse-transition='false')
.editor-props-codeeditor-title .editor-props-codeeditor-title
@ -276,10 +276,10 @@ export default {
currentTab: 0, currentTab: 0,
cm: null, cm: null,
rules: { rules: {
required: value => !!value || 'This field is required.', required: value => !!value || 'This field is required.',
path: value => { 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.' return filenamePattern.test(value) || 'Invalid path. Please ensure it does not contain special characters, or begin/end in a slash or hashtag string.'
} }
} }
} }
}, },
@ -334,7 +334,7 @@ export default {
this.loadEditor(this.$refs.codejs, 'html') this.loadEditor(this.$refs.codejs, 'html')
}, 100) }, 100)
}) })
} else if (newValue === 4) { } else if (newValue === 3) {
this.$nextTick(() => { this.$nextTick(() => {
setTimeout(() => { setTimeout(() => {
this.loadEditor(this.$refs.codecss, 'css') this.loadEditor(this.$refs.codecss, 'css')

@ -641,19 +641,25 @@ export default {
} else { } else {
this.loaderColor = 'green darken-1' this.loaderColor = 'green darken-1'
this.loaderTitle = this.$t('auth:loginSuccess') this.loaderTitle = this.$t('auth:loginSuccess')
Cookies.set('jwt', respObj.jwt, { expires: 365 }) Cookies.set('jwt', respObj.jwt, { expires: 365, secure: window.location.protocol === 'https:' })
_.delay(() => { _.delay(() => {
const loginRedirect = Cookies.get('loginRedirect') const loginRedirect = Cookies.get('loginRedirect')
const isValidRedirect = loginRedirect && loginRedirect.startsWith('/') && !loginRedirect.startsWith('//') && !loginRedirect.includes('://')
if (loginRedirect === '/' && respObj.redirect) { if (loginRedirect === '/' && respObj.redirect) {
Cookies.remove('loginRedirect') Cookies.remove('loginRedirect')
window.location.replace(respObj.redirect) window.location.replace(respObj.redirect)
} else if (loginRedirect) { } else if (isValidRedirect) {
Cookies.remove('loginRedirect') Cookies.remove('loginRedirect')
window.location.replace(loginRedirect) window.location.replace(loginRedirect)
} else if (respObj.redirect) {
window.location.replace(respObj.redirect)
} else { } else {
window.location.replace('/') if (loginRedirect) {
Cookies.remove('loginRedirect')
}
if (respObj.redirect) {
window.location.replace(respObj.redirect)
} else {
window.location.replace('/')
}
} }
}, 1000) }, 1000)
} }

@ -129,41 +129,43 @@
//- v-btn(color='purple darken-4', disabled).ml-0 Enable 2FA //- v-btn(color='purple darken-4', disabled).ml-0 Enable 2FA
//- v-btn(color='purple darken-4', dark, depressed, disabled).ml-0 Disable 2FA //- v-btn(color='purple darken-4', dark, depressed, disabled).ml-0 Disable 2FA
template(v-if='user.providerKey === `local`') template(v-if='user.providerKey === `local`')
v-divider.mt-3 form#change-password-form(@submit.prevent='changePassword')
v-subheader.pl-0: span.subtitle-2 {{$t('profile:auth.changePassword')}} v-divider.mt-3
v-text-field( v-subheader.pl-0: span.subtitle-2 {{$t('profile:auth.changePassword')}}
ref='iptCurrentPass' v-text-field(
v-model='currentPass' ref='iptCurrentPass'
outlined v-model='currentPass'
:label='$t(`profile:auth.currentPassword`)' outlined
type='password' :label='$t(`profile:auth.currentPassword`)'
prepend-inner-icon='mdi-form-textbox-password' type='password'
) prepend-inner-icon='mdi-form-textbox-password'
v-text-field( autocomplete='current-password'
ref='iptNewPass' )
v-model='newPass' v-text-field(
outlined ref='iptNewPass'
:label='$t(`profile:auth.newPassword`)' v-model='newPass'
type='password' outlined
prepend-inner-icon='mdi-form-textbox-password' :label='$t(`profile:auth.newPassword`)'
autocomplete='off' type='password'
counter='255' prepend-inner-icon='mdi-form-textbox-password'
loading autocomplete='off'
) counter='255'
password-strength(slot='progress', v-model='newPass') loading
v-text-field( )
ref='iptVerifyPass' password-strength(slot='progress', v-model='newPass')
v-model='verifyPass' v-text-field(
outlined ref='iptVerifyPass'
:label='$t(`profile:auth.verifyPassword`)' v-model='verifyPass'
type='password' outlined
prepend-inner-icon='mdi-form-textbox-password' :label='$t(`profile:auth.verifyPassword`)'
autocomplete='off' type='password'
hide-details prepend-inner-icon='mdi-form-textbox-password'
) autocomplete='off'
hide-details
)
v-card-chin(v-if='user.providerKey === `local`') v-card-chin(v-if='user.providerKey === `local`')
v-spacer v-spacer
v-btn.px-4(color='purple darken-4', dark, depressed, @click='changePassword', :loading='changePassLoading') v-btn.px-4(color='purple darken-4', dark, depressed, :loading='changePassLoading', type='submit', form='change-password-form')
v-icon(left) mdi-progress-check v-icon(left) mdi-progress-check
span {{$t('profile:auth.changePassword')}} span {{$t('profile:auth.changePassword')}}
v-flex(lg6 xs12) v-flex(lg6 xs12)
@ -755,7 +757,7 @@ export default {
}) })
const resp = _.get(respRaw, 'data.users.updateProfile.responseResult', {}) const resp = _.get(respRaw, 'data.users.updateProfile.responseResult', {})
if (resp.succeeded) { if (resp.succeeded) {
Cookies.set('jwt', _.get(respRaw, 'data.users.updateProfile.jwt', ''), { expires: 365 }) Cookies.set('jwt', _.get(respRaw, 'data.users.updateProfile.jwt', ''), { expires: 365, secure: window.location.protocol === 'https:' })
this.$store.set('user/name', this.user.name) this.$store.set('user/name', this.user.name)
this.$store.commit('showNotification', { this.$store.commit('showNotification', {
message: this.$t('profile:save.success'), message: this.$t('profile:save.success'),
@ -863,7 +865,7 @@ export default {
this.currentPass = '' this.currentPass = ''
this.newPass = '' this.newPass = ''
this.verifyPass = '' this.verifyPass = ''
Cookies.set('jwt', _.get(respRaw, 'data.users.changePassword.jwt', ''), { expires: 365 }) Cookies.set('jwt', _.get(respRaw, 'data.users.changePassword.jwt', ''), { expires: 365, secure: window.location.protocol === 'https:' })
this.$store.commit('showNotification', { this.$store.commit('showNotification', {
message: this.$t('profile:auth.changePassSuccess'), message: this.$t('profile:auth.changePassSuccess'),
style: 'success', style: 'success',

@ -367,6 +367,8 @@ import _ from 'lodash'
import ClipboardJS from 'clipboard' import ClipboardJS from 'clipboard'
import Vue from 'vue' import Vue from 'vue'
/* global siteLangs */
Vue.component('Tabset', Tabset) Vue.component('Tabset', Tabset)
Prism.plugins.autoloader.languages_path = '/_assets/js/prism/' Prism.plugins.autoloader.languages_path = '/_assets/js/prism/'
@ -493,6 +495,7 @@ export default {
}, },
data() { data() {
return { return {
locales: siteLangs,
navShown: false, navShown: false,
navExpanded: false, navExpanded: false,
upBtnShown: false, upBtnShown: false,
@ -537,13 +540,14 @@ export default {
} }
}, },
breadcrumbs() { breadcrumbs() {
return [{ path: '/', name: 'Home' }].concat(_.reduce(this.path.split('/'), (result, value, key) => { return [{ path: '/', name: 'Home' }].concat(
result.push({ _.reduce(this.path.split('/'), (result, value) => {
path: _.get(_.last(result), 'path', `/${this.locale}`) + `/${value}`, result.push({
name: value path: _.get(_.last(result), 'path', this.locales.length > 0 ? `/${this.locale}` : '') + `/${value}`,
}) name: value
return result })
}, [])) return result
}, []))
}, },
pageUrl () { return window.location.href }, pageUrl () { return window.location.href },
upBtnPosition () { upBtnPosition () {
@ -649,7 +653,11 @@ export default {
}, },
methods: { methods: {
goHome () { goHome () {
window.location.assign('/') if (this.locales && this.locales.length > 0) {
window.location.assign(`/${this.locale}/home`)
} else {
window.location.assign('/')
}
}, },
toggleNavigation () { toggleNavigation () {
this.navOpen = !this.navOpen this.navOpen = !this.navOpen

@ -764,8 +764,17 @@
.diagram { .diagram {
margin-top: 1rem; margin-top: 1rem;
overflow: auto; overflow: auto;
svg:first-child {
direction: ltr; svg {
color-scheme: light !important;
&:first-child {
direction: ltr;
}
@at-root .theme--dark & {
color-scheme: dark !important;
}
} }
} }
@ -1275,6 +1284,8 @@
color: #000; color: #000;
box-shadow: none; box-shadow: none;
text-shadow: none; text-shadow: none;
white-space: pre-wrap !important;
overflow-wrap: break-word !important;
} }
} }
} }

@ -1,7 +1,7 @@
# ========================= # =========================
# --- BUILD NPM MODULES --- # --- BUILD NPM MODULES ---
# ========================= # =========================
FROM node:20-alpine AS build FROM node:24-alpine AS build
RUN apk add yarn g++ make cmake python3 --no-cache RUN apk add yarn g++ make cmake python3 --no-cache
@ -16,7 +16,7 @@ RUN yarn patch-package
# =============== # ===============
# --- Release --- # --- Release ---
# =============== # ===============
FROM node:20-alpine FROM node:24-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 && \

@ -1,7 +1,7 @@
# ==================== # ====================
# --- Build Assets --- # --- Build Assets ---
# ==================== # ====================
FROM node:20-alpine AS assets FROM node:24-alpine AS assets
RUN apk add yarn g++ make cmake python3 --no-cache RUN apk add yarn g++ make cmake python3 --no-cache
@ -25,7 +25,7 @@ RUN yarn patch-package
# =============== # ===============
# --- Release --- # --- Release ---
# =============== # ===============
FROM node:20-alpine FROM node:24-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 && \
@ -53,4 +53,4 @@ EXPOSE 3443
# HEALTHCHECK --interval=30s --timeout=30s --start-period=30s --retries=3 CMD curl -f http://localhost:3000/healthz # HEALTHCHECK --interval=30s --timeout=30s --start-period=30s --retries=3 CMD curl -f http://localhost:3000/healthz
CMD ["node", "server"] CMD ["node", "--no-deprecation", "server"]

@ -1,7 +1,7 @@
# -- DEV DOCKERFILE -- # -- DEV DOCKERFILE --
# -- DO NOT USE IN PRODUCTION! -- # -- DO NOT USE IN PRODUCTION! --
FROM node:18 FROM node:24
LABEL maintainer "requarks.io" LABEL maintainer "requarks.io"
RUN apt-get update && \ RUN apt-get update && \

@ -5,7 +5,7 @@ version: "3"
services: services:
db: db:
container_name: wiki-db container_name: wiki-db
image: postgres:15-alpine image: postgres:17-alpine
environment: environment:
POSTGRES_DB: wiki POSTGRES_DB: wiki
POSTGRES_PASSWORD: wikijsrocks POSTGRES_PASSWORD: wikijsrocks

@ -1,6 +0,0 @@
dependencies:
- name: postgresql
repository: https://charts.bitnami.com/bitnami
version: 8.10.14
digest: sha256:db7c1e0bc9ec0ed45520521bd76bb390d04711fd0f04affaadafa1dc498ce68b
generated: "2020-07-21T20:34:41.41180748-04:00"

@ -1,11 +1,7 @@
apiVersion: v2 apiVersion: v2
name: wiki name: wiki
# This is the chart version. This version number should be incremented each time you make changes version: '3.0.0'
# to the chart and its templates, including the app version. appVersion: '2'
version: 2.3.0
# This is the version number of the application being deployed. This version number should be
# incremented each time you make changes to the application.
AppVersion: latest
description: The most powerful and extensible open source Wiki software. description: The most powerful and extensible open source Wiki software.
keywords: keywords:
- wiki - wiki
@ -14,29 +10,8 @@ keywords:
- docs - docs
- reference - reference
- editor - editor
# A chart can be either an 'application' or a 'library' chart.
#
# Application charts are a collection of templates that can be packaged into versioned archives
# to be deployed.
#
# Library charts provide useful utilities or functions for the chart developer. They're included as
# a dependency of application charts to inject those utilities and functions into the rendering
# pipeline. Library charts do not define any templates and therefore cannot be deployed.
type: application type: application
dependencies: home: https://js.wiki
- name: postgresql
version: 8.10.14
repository: https://charts.bitnami.com/bitnami
condition: postgresql.enabled
home: https://wiki.js.org
icon: https://cdn.js.wiki/images/wikijs-butterfly.svg icon: https://cdn.js.wiki/images/wikijs-butterfly.svg
sources: sources:
- https://github.com/Requarks/wiki - https://github.com/requarks/wiki
maintainers:
- name: Nicolas Giard
email: github@ngpixel.com
url: https://github.com/NGPixel
- name: James Greenhill
email: james@fuziontech.net
url: https://github.com/fuziontech
engine: gotpl

@ -43,7 +43,7 @@ Wiki.js is an open source project that has been made possible due to the generou
This chart bootstraps a Wiki.js deployment on a [Kubernetes](http://kubernetes.io) cluster using the [Helm](https://helm.sh) package manager. This chart bootstraps a Wiki.js deployment on a [Kubernetes](http://kubernetes.io) cluster using the [Helm](https://helm.sh) package manager.
It also optionally packages the [PostgreSQL](https://github.com/kubernetes/charts/tree/master/stable/postgresql) as the database but you are free to bring your own. It also optionally deploys PostgreSQL as the database using the official PostgreSQL image from Docker Hub, but you are free to bring your own database.
## Prerequisites ## Prerequisites
@ -59,7 +59,7 @@ $ helm repo add requarks https://charts.js.wiki
To install the chart with the release name `my-release` run the following: To install the chart with the release name `my-release` run the following:
### Using Helm 3: ### Using Helm 3/4:
```console ```console
$ helm install my-release requarks/wiki $ helm install my-release requarks/wiki
``` ```
@ -95,7 +95,7 @@ The following table lists the configurable parameters of the Wiki.js chart and t
| Parameter | Description | Default | | Parameter | Description | Default |
| ------------------------------- | ------------------------------- | ---------------------------------------------------------- | | ------------------------------- | ------------------------------- | ---------------------------------------------------------- |
| `image.repository` | Wiki.js image | `requarks/wiki` | | `image.repository` | Wiki.js image | `requarks/wiki` |
| `image.tag` | Wiki.js image tag | `latest` | | `image.tag` | Wiki.js image tag | `2` |
| `imagePullPolicy` | Image pull policy | `IfNotPresent` | | `imagePullPolicy` | Image pull policy | `IfNotPresent` |
| `replicacount` | Number of Wiki.js pods to run | `1` | | `replicacount` | Number of Wiki.js pods to run | `1` |
| `revisionHistoryLimit` | Total number of revision history points | `10` | | `revisionHistoryLimit` | Total number of revision history points | `10` |
@ -119,20 +119,33 @@ The following table lists the configurable parameters of the Wiki.js chart and t
| `sideload.resources.limits` | Resource limits for the sideload container | `nil` | | `sideload.resources.limits` | Resource limits for the sideload container | `nil` |
| `sideload.resources.requests` | Resource requests for the sideload container | `nil` | | `sideload.resources.requests` | Resource requests for the sideload container | `nil` |
| `nodeExtraCaCerts` | Trusted certificates path | `nil` | | `nodeExtraCaCerts` | Trusted certificates path | `nil` |
| `externalPostgresql.databaseURL` | External postgres connection string | `nil` |
| `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` |
| `postgresql.postgresqlHost` | External postgres host | `nil` | | `postgresql.postgresqlHost` | Postgres host | `nil` |
| `postgresql.postgresqlPassword` | External postgres password | `nil` | | `postgresql.postgresqlPassword` | Postgres password | `nil` |
| `postgresql.existingSecret` | Provide an existing `Secret` for postgres | `nil` | | `postgresql.existingSecret` | Provide an existing `Secret` for postgres | `nil` |
| `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.existingSecretUserKey` | The postgres username key in the existing `Secret` | `postgresql-username` |
| `postgresql.postgresqlPort` | Postgres port | `5432` |
| `postgresql.ssl` | Enable external postgres SSL connection | `false` | | `postgresql.ssl` | Enable external postgres SSL connection | `false` |
| `postgresql.ca` | Certificate of Authority content 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` |
| `postgresql.persistence.size` | Postgers PVC Storage Request | `8Gi` | | `postgresql.persistence.size` | Postgres PVC Storage Request | `8Gi` |
| `postgresql.persistence.accessMode` | Postgres Persistent Volume Access Mode | `ReadWriteOnce` |
| `postgresql.image.repository` | PostgreSQL image repository | `postgres` |
| `postgresql.image.tag` | PostgreSQL image tag | `18` |
| `postgresql.image.pullPolicy` | PostgreSQL image pull policy | `IfNotPresent` |
| `postgresql.resources` | PostgreSQL resource requests/limits | `{}` |
| `postgresql.nodeSelector` | PostgreSQL node selector labels | `{}` |
| `postgresql.tolerations` | PostgreSQL toleration labels | `[]` |
| `postgresql.affinity` | PostgreSQL affinity settings | `{}` |
| `postgresql.service.type` | PostgreSQL service type | `ClusterIP` |
| `postgresql.service.port` | PostgreSQL service port | `5432` |
| `postgresql.service.annotations` | PostgreSQL service annotations | `{}` |
Specify each parameter using the `--set key=value[,key=value]` argument to `helm install`. For example, Specify each parameter using the `--set key=value[,key=value]` argument to `helm install`. For example,
@ -150,25 +163,44 @@ $ 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)
## PostgresSQL ## PostgreSQL
By default, PostgreSQL is installed as part of the chart. By default, PostgreSQL is installed as part of the chart using the official PostgreSQL image from Docker Hub (version 18).
### Using an external PostgreSQL server ### Using an external PostgreSQL server
To use an external PostgreSQL server, set `postgresql.enabled` to `false` and then set `postgresql.postgresqlHost` and `postgresql.postgresqlPassword`. To use an existing `Secret`, set `postgresql.existingSecret`. The other options (`postgresql.postgresqlDatabase`, `postgresql.postgresqlUser`, `postgresql.postgresqlPort` and `postgresql.existingSecretKey`) may also want changing from their default values. To use an external PostgreSQL server, set `postgresql.enabled` to `false`, then use either:
To use an SSL connection you can set `postgresql.ssl` to `true` and if needed the path to a Certificate of Authority can be set using `postgresql.ca` to `/path/to/ca`. Default `postgresql.ssl` value is `false`. #### Connection String
If `postgresql.existingSecret` is not specified, you also need to add the following Helm template to your deployment in order to create the postgresql `Secret`: Set `externalPostgresql.databaseURL` to the full PostgreSQL connection string.
```yaml #### Connection Parameters
kind: Secret
apiVersion: v1 Set `externalPostgresql.host`, `externalPostgres.port`, `externalPostgres.database`, `externalPostgres.username`, `externalPostgres.existingSecret` *(secret name)* and `externalPostgres.existingSecretKey` *(key in the secret containing the password)*
metadata:
name: {{ template "wiki.postgresql.secret" . }} Ensure the secret specified in `externalPostgresql.existingSecret` already exists, with a password set at the path specified in `externalPostgres.existingSecretKey`.
data:
{{ template "wiki.postgresql.secretKey" . }}: "{{ .Values.postgresql.postgresqlPassword | b64enc }}" To use an SSL connection you can set `externalPostgresql.ssl` to `true` and if needed the path to a Certificate of Authority can be set using `externalPostgresql.ca` to `/path/to/ca`. Default `externalPostgresql.ssl` value is `false`.
### Using an existing PostgreSQL secret with built-in PostgreSQL
When using the built-in PostgreSQL (default behavior with `postgresql.enabled: true`), you can still use an existing Kubernetes secret for the database credentials by setting:
- `postgresql.existingSecret`: Name of the existing secret containing the credentials
- `postgresql.existingSecretKey`: Key in the secret containing the password (defaults to `postgresql-password`)
- `postgresql.existingSecretUserKey`: Key in the secret containing the username (defaults to `postgresql-username`)
Example usage:
```bash
# Create your existing secret
kubectl create secret generic my-postgres-secret \
--from-literal=postgresql-username=postgres \
--from-literal=postgresql-password=yourpassword
# Deploy with existing secret
helm install my-release requarks/wiki \
--set postgresql.enabled=true \
--set postgresql.existingSecret=my-postgres-secret
``` ```
## Persistence ## Persistence

@ -19,3 +19,16 @@
echo "Visit http://127.0.0.1:8080 to use your application" echo "Visit http://127.0.0.1:8080 to use your application"
kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:80 kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:80
{{- end }} {{- end }}
{{- if .Values.postgresql.enabled }}
2. PostgreSQL database has been deployed as part of this release:
- Database: {{ .Values.postgresql.postgresqlDatabase }}
- User: {{ .Values.postgresql.postgresqlUser }}
- Service: {{ include "wiki.postgresql.fullname" . }}
- Version: {{ .Values.postgresql.image.tag }}
- Persistence: {{ .Values.postgresql.persistence.enabled | ternary "Enabled" "Disabled" }}
{{- end }}
{{- if not .Values.postgresql.enabled }}
2. External PostgreSQL setup detected. Ensure your database is accessible at the configured host.
{{- end }}

@ -63,15 +63,18 @@ Create the name of the service account to use
{{- end -}} {{- end -}}
{{/* {{/*
Create a default fully qualified app name. PostgreSQL fullname
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
*/}} */}}
{{- define "wiki.postgresql.fullname" -}} {{- define "wiki.postgresql.fullname" -}}
{{- if .Values.postgresql.fullnameOverride -}} {{- printf "%s-%s" (include "wiki.fullname" .) "postgresql" | trunc 63 | trimSuffix "-" -}}
{{- .Values.postgresql.fullnameOverride | trunc 63 | trimSuffix "-" -}}
{{- else -}}
{{ printf "%s-%s" .Release.Name "postgresql"}}
{{- end -}} {{- end -}}
{{/*
PostgreSQL selector labels
*/}}
{{- define "wiki.postgresql.selectorLabels" -}}
app.kubernetes.io/name: {{ include "wiki.name" . }}-postgresql
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end -}} {{- end -}}
{{/* {{/*
@ -79,9 +82,9 @@ Set postgres host
*/}} */}}
{{- define "wiki.postgresql.host" -}} {{- define "wiki.postgresql.host" -}}
{{- if .Values.postgresql.enabled -}} {{- if .Values.postgresql.enabled -}}
{{- template "wiki.postgresql.fullname" . -}} {{- include "wiki.postgresql.fullname" . -}}
{{- else -}} {{- else -}}
{{- .Values.postgresql.postgresqlHost | quote -}} {{- .Values.postgresql.postgresqlHost | default "localhost" | quote -}}
{{- end -}} {{- end -}}
{{- end -}} {{- end -}}
@ -89,10 +92,25 @@ Set postgres host
Set postgres secret Set postgres secret
*/}} */}}
{{- define "wiki.postgresql.secret" -}} {{- define "wiki.postgresql.secret" -}}
{{- if .Values.postgresql.enabled -}} {{- if and .Values.postgresql.enabled .Values.postgresql.existingSecret -}}
{{- template "wiki.postgresql.fullname" . -}} {{- .Values.postgresql.existingSecret -}}
{{- else if .Values.postgresql.enabled -}}
{{- include "wiki.postgresql.fullname" . -}}
{{- else -}} {{- else -}}
{{- template "wiki.fullname" . -}} {{- template "wiki.fullname" . -}}
{{- end -}}
{{- end -}}
{{/*
Set postgres secretUserKey
*/}}
{{- define "wiki.postgresql.secretUserKey" -}}
{{- if and .Values.postgresql.enabled .Values.postgresql.existingSecret -}}
{{- default "postgresql-username" .Values.postgresql.existingSecretUserKey | quote -}}
{{- else if .Values.postgresql.enabled -}}
"postgresql-username"
{{- else -}}
{{- default "postgresql-username" .Values.postgresql.existingSecretUserKey | quote -}}
{{- end -}} {{- end -}}
{{- end -}} {{- end -}}
@ -100,9 +118,24 @@ Set postgres secret
Set postgres secretKey Set postgres secretKey
*/}} */}}
{{- define "wiki.postgresql.secretKey" -}} {{- define "wiki.postgresql.secretKey" -}}
{{- if .Values.postgresql.enabled -}} {{- if and .Values.postgresql.enabled .Values.postgresql.existingSecret -}}
"postgresql-password" {{- default "postgresql-password" .Values.postgresql.existingSecretKey | quote -}}
{{- else if .Values.postgresql.enabled -}}
"postgresql-password"
{{- else -}}
{{- default "postgresql-password" .Values.postgresql.existingSecretKey | quote -}}
{{- end -}}
{{- end -}}
{{/*
Set postgres secretDatabaseKey
*/}}
{{- define "wiki.postgresql.secretDatabaseKey" -}}
{{- if and .Values.postgresql.enabled .Values.postgresql.existingSecret -}}
{{- default "postgresql-database" .Values.postgresql.existingSecretDatabaseKey | quote -}}
{{- else if .Values.postgresql.enabled -}}
"postgresql-database"
{{- else -}} {{- else -}}
{{- default "postgresql-password" .Values.postgresql.existingSecretKey | quote -}} {{- default "postgresql-database" .Values.postgresql.existingSecretDatabaseKey | quote -}}
{{- end -}} {{- end -}}
{{- end -}} {{- end -}}

@ -29,7 +29,7 @@ spec:
- name: {{ .Chart.Name }}-sideload - name: {{ .Chart.Name }}-sideload
securityContext: securityContext:
{{- toYaml .Values.sideload.securityContext | nindent 12 }} {{- toYaml .Values.sideload.securityContext | nindent 12 }}
image: "{{ .Values.image.repository }}:{{ default "latest" .Values.image.tag }}" image: "{{ .Values.image.repository }}:{{ default "2" .Values.image.tag }}"
imagePullPolicy: {{ default "IfNotPresent" .Values.image.imagePullPolicy }} imagePullPolicy: {{ default "IfNotPresent" .Values.image.imagePullPolicy }}
env: env:
{{- toYaml .Values.sideload.env | nindent 12 }} {{- toYaml .Values.sideload.env | nindent 12 }}
@ -42,7 +42,7 @@ spec:
- name: {{ .Chart.Name }} - name: {{ .Chart.Name }}
securityContext: securityContext:
{{- toYaml .Values.securityContext | nindent 12 }} {{- toYaml .Values.securityContext | nindent 12 }}
image: "{{ .Values.image.repository }}:{{ default "latest" .Values.image.tag }}" image: "{{ .Values.image.repository }}:{{ default "2" .Values.image.tag }}"
imagePullPolicy: {{ default "IfNotPresent" .Values.image.imagePullPolicy }} imagePullPolicy: {{ default "IfNotPresent" .Values.image.imagePullPolicy }}
env: env:
{{- if .Values.nodeExtraCaCerts }} {{- if .Values.nodeExtraCaCerts }}
@ -56,15 +56,22 @@ spec:
value: {{ .Values.externalPostgresql.databaseURL }} value: {{ .Values.externalPostgresql.databaseURL }}
- name: NODE_TLS_REJECT_UNAUTHORIZED - name: NODE_TLS_REJECT_UNAUTHORIZED
value: {{ default "1" .Values.externalPostgresql.NODE_TLS_REJECT_UNAUTHORIZED | quote }} value: {{ default "1" .Values.externalPostgresql.NODE_TLS_REJECT_UNAUTHORIZED | quote }}
{{- else }} {{- else if .Values.postgresql.enabled }}
- name: DB_HOST - name: DB_HOST
value: {{ template "wiki.postgresql.host" . }} value: {{ template "wiki.postgresql.host" . }}
- name: DB_PORT - name: DB_PORT
value: "{{ default "5432" .Values.postgresql.postgresqlPort }}" value: "{{ default "5432" .Values.postgresql.postgresqlPort }}"
- name: DB_NAME - name: DB_NAME
value: {{ default "wiki" .Values.postgresql.postgresqlDatabase }} value: {{ default "wiki" .Values.postgresql.postgresqlDatabase | quote }}
- name: DB_USER - name: DB_USER
value: {{ default "wiki" .Values.postgresql.postgresqlUser }} {{- if .Values.postgresql.existingSecret }}
valueFrom:
secretKeyRef:
name: {{ .Values.postgresql.existingSecret }}
key: {{ template "wiki.postgresql.secretUserKey" . }}
{{- else }}
value: {{ default "postgres" .Values.postgresql.postgresqlUser }}
{{- end }}
- name: DB_SSL - name: DB_SSL
value: "{{ default "false" .Values.postgresql.ssl }}" value: "{{ default "false" .Values.postgresql.ssl }}"
- name: DB_SSL_CA - name: DB_SSL_CA
@ -72,15 +79,33 @@ spec:
- name: DB_PASS - name: DB_PASS
valueFrom: valueFrom:
secretKeyRef: secretKeyRef:
{{- if .Values.postgresql.existingSecret }}
name: {{ .Values.postgresql.existingSecret }}
{{- else }}
name: {{ template "wiki.postgresql.secret" . }} name: {{ template "wiki.postgresql.secret" . }}
{{- end }}
key: {{ template "wiki.postgresql.secretKey" . }} key: {{ template "wiki.postgresql.secretKey" . }}
{{- else if .Values.externalPostgresql }}
# External PostgreSQL configuration
- name: DB_HOST
value: {{ required "External PostgreSQL host is required when postgresql.enabled is false" .Values.externalPostgresql.host | quote }}
- name: DB_PORT
value: {{ required "External PostgreSQL port is required when postgresql.enabled is false" .Values.externalPostgresql.port | quote }}
- name: DB_NAME
value: {{ required "External PostgreSQL database name is required when postgresql.enabled is false" .Values.externalPostgresql.database | quote }}
- name: DB_USER
value: {{ required "External PostgreSQL user is required when postgresql.enabled is false" .Values.externalPostgresql.username | quote }}
- name: DB_PASS
valueFrom:
secretKeyRef:
name: {{ required "External PostgreSQL secret name is required when postgresql.enabled is false" .Values.externalPostgresql.existingSecret | quote }}
key: {{ required "External PostgreSQL secret key is required when postgresql.enabled is false" .Values.externalPostgresql.existingSecretKey | quote }}
- name: DB_SSL
value: "{{ default "false" .Values.externalPostgresql.ssl }}"
- name: DB_SSL_CA
value: "{{ default "" .Values.externalPostgresql.ca }}"
{{- end }} {{- end }}
- name: HA_ACTIVE - name: HA_ACTIVE
value: {{ .Values.replicaCount | int | le 2 | quote }} value: {{ .Values.replicaCount | int | le 2 | quote }}
{{- with .Values.extraEnvVars }}
{{- toYaml . | nindent 12 }}
{{- end }}
{{- with .Values.volumeMounts }} {{- with .Values.volumeMounts }}
volumeMounts: volumeMounts:
{{- toYaml . | nindent 12 }} {{- toYaml . | nindent 12 }}

@ -0,0 +1,21 @@
{{- if and .Values.postgresql.enabled .Values.postgresql.persistence.enabled -}}
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: {{ include "wiki.postgresql.fullname" . }}
labels:
{{- include "wiki.labels" . | nindent 4 }}
spec:
accessModes:
- {{ .Values.postgresql.persistence.accessMode | quote }}
resources:
requests:
storage: {{ .Values.postgresql.persistence.size | quote }}
{{- if .Values.postgresql.persistence.storageClass }}
{{- if (eq "-" .Values.postgresql.persistence.storageClass) }}
storageClassName: ""
{{- else }}
storageClassName: {{ .Values.postgresql.persistence.storageClass | quote }}
{{- end }}
{{- end }}
{{- end }}

@ -0,0 +1,12 @@
{{- if and .Values.postgresql.enabled (not .Values.postgresql.existingSecret) -}}
apiVersion: v1
kind: Secret
metadata:
name: {{ include "wiki.postgresql.fullname" . }}
labels:
{{- include "wiki.labels" . | nindent 4 }}
type: Opaque
data:
postgresql-password: {{ .Values.postgresql.postgresqlPassword | b64enc | quote }}
postgresql-username: {{ .Values.postgresql.postgresqlUser | b64enc | quote }}
{{- end }}

@ -0,0 +1,21 @@
{{- if .Values.postgresql.enabled -}}
apiVersion: v1
kind: Service
metadata:
name: {{ include "wiki.postgresql.fullname" . }}
labels:
{{- include "wiki.labels" . | nindent 4 }}
{{- with .Values.postgresql.service.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
type: {{ .Values.postgresql.service.type }}
ports:
- port: {{ .Values.postgresql.service.port }}
targetPort: 5432
protocol: TCP
name: postgresql
selector:
{{- include "wiki.postgresql.selectorLabels" . | nindent 4 }}
{{- end }}

@ -0,0 +1,101 @@
{{- if .Values.postgresql.enabled -}}
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: {{ include "wiki.postgresql.fullname" . }}
labels:
{{- include "wiki.labels" . | nindent 4 }}
spec:
serviceName: {{ include "wiki.postgresql.fullname" . }}
replicas: 1
selector:
matchLabels:
{{- include "wiki.postgresql.selectorLabels" . | nindent 6 }}
template:
metadata:
labels:
{{- include "wiki.postgresql.selectorLabels" . | nindent 8 }}
spec:
{{- with .Values.postgresql.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.postgresql.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.postgresql.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}
containers:
- name: postgresql
image: {{ .Values.postgresql.image.repository }}:{{ .Values.postgresql.image.tag }}
imagePullPolicy: {{ .Values.postgresql.image.pullPolicy }}
ports:
- containerPort: 5432
name: postgresql
env:
- name: POSTGRES_DB
value: {{ .Values.postgresql.postgresqlDatabase | quote }}
- name: POSTGRES_USER
{{- if .Values.postgresql.existingSecret }}
valueFrom:
secretKeyRef:
name: {{ .Values.postgresql.existingSecret }}
key: {{ default "postgresql-username" .Values.postgresql.existingSecretUserKey | quote }}
{{- else }}
valueFrom:
secretKeyRef:
name: {{ include "wiki.postgresql.fullname" . }}
key: postgresql-username
{{- end }}
- name: POSTGRES_PASSWORD
{{- if .Values.postgresql.existingSecret }}
valueFrom:
secretKeyRef:
name: {{ .Values.postgresql.existingSecret }}
key: {{ default "postgresql-password" .Values.postgresql.existingSecretKey | quote }}
{{- else }}
valueFrom:
secretKeyRef:
name: {{ include "wiki.postgresql.fullname" . }}
key: postgresql-password
{{- end }}
- name: PGDATA
value: /var/lib/postgresql/data/pgdata
livenessProbe:
exec:
command:
- sh
- -c
- exec pg_isready -U {{ .Values.postgresql.postgresqlUser }} -d {{ .Values.postgresql.postgresqlDatabase }}
initialDelaySeconds: 60
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 6
readinessProbe:
exec:
command:
- sh
- -c
- exec pg_isready -U {{ .Values.postgresql.postgresqlUser }} -d {{ .Values.postgresql.postgresqlDatabase }}
initialDelaySeconds: 5
periodSeconds: 5
timeoutSeconds: 3
failureThreshold: 6
resources:
{{- toYaml .Values.postgresql.resources | nindent 12 }}
volumeMounts:
- name: postgresql-data
mountPath: /var/lib/postgresql/data
subPath: postgresql
volumes:
- name: postgresql-data
{{- if .Values.postgresql.persistence.enabled }}
persistentVolumeClaim:
claimName: {{ include "wiki.postgresql.fullname" . }}
{{- else }}
emptyDir: {}
{{- end }}
{{- end }}

@ -19,8 +19,4 @@ spec:
targetPort: http targetPort: http
protocol: TCP protocol: TCP
name: http name: http
- port: {{ default "443" .Values.service.httpsPort}}
targetPort: http
protocol: TCP
name: https
selector: {{- include "wiki.selectorLabels" . | nindent 4}} selector: {{- include "wiki.selectorLabels" . | nindent 4}}

@ -3,7 +3,7 @@
# Declare variables to be passed into your templates. # Declare variables to be passed into your templates.
replicaCount: 1 replicaCount: 1
revisionHistoryLimit: 10 revisionHistoryLimit: 2
image: image:
repository: requarks/wiki repository: requarks/wiki
@ -61,7 +61,6 @@ service:
# Annotations applied for services such as externalDNS or # Annotations applied for services such as externalDNS or
# service type LoadBalancer # service type LoadBalancer
# type: LoadBalancer # type: LoadBalancer
# httpsPort: 443
# annotations: {} # annotations: {}
# loadBalancerIP: 172.16.0.1 # loadBalancerIP: 172.16.0.1
@ -108,7 +107,7 @@ volumes: []
sideload: sideload:
enabled: false enabled: false
# Git-Repo containing all locales.json-files you need: # Git-Repo containing all locales.json-files you need:
repoURL: https://github.com/Requarks/wiki-localization repoURL: https://github.com/requarks/wiki-localization
## This can be helpfull if you have internet access over a http proxy: ## This can be helpfull if you have internet access over a http proxy:
env: [] env: []
@ -138,6 +137,17 @@ sideload:
## Append extra trusted certificates for node process from extra volume via NODE_EXTRA_CA_CERTS variable ## Append extra trusted certificates for node process from extra volume via NODE_EXTRA_CA_CERTS variable
# nodeExtraCaCerts: "/path/to/certs.pem" # nodeExtraCaCerts: "/path/to/certs.pem"
## Additional environment variables to set
extraEnvVars: []
# extraEnvVars:
# - name: CUSTOM_VAR
# value: "custom_value"
# - name: SECRET_VAR
# valueFrom:
# secretKeyRef:
# name: my-secret
# key: secret-key
## This will override the postgresql chart values ## This will override the postgresql chart values
# externalPostgresql: # externalPostgresql:
# # note: ?sslmode=require => ?ssl=true # # note: ?sslmode=require => ?ssl=true
@ -145,47 +155,52 @@ sideload:
# # For self signed CAs, like DigitalOcean # # For self signed CAs, like DigitalOcean
# NODE_TLS_REJECT_UNAUTHORIZED: "0" # NODE_TLS_REJECT_UNAUTHORIZED: "0"
## Configuration values for the postgresql dependency. ## Configuration for the custom PostgreSQL 18 deployment
## ref: https://github.com/kubernetes/charts/blob/master/stable/postgresql/README.md
## ##
postgresql: postgresql:
## Use the PostgreSQL chart dependency.
## Set to false if bringing your own PostgreSQL, and set secret value postgresql-uri.
##
enabled: true enabled: true
## ssl enforce SSL communication with PostgresSQL ## ssl enforce SSL communication with PostgresSQL
## Default to false ## Default to false
## ##
# ssl: false ssl: false
## ca Certificate of Authority ## ca Certificate of Authority
## Default to empty, point to location of CA ## Default to empty, point to location of CA
## ##
# ca: "path to ca" # ca: "path to ca"
## postgresqlHost override postgres database host ## postgresqlHost override postgres database host
## Default to postgres ## Default to the service name of the custom PostgreSQL deployment
## ##
# postgresqlHost: postgres postgresqlHost: "{{ include \"wiki.postgresql.fullname\" . }}"
## postgresqlPort port for postgres ## postgresqlPort port for postgres
## Default to 5432 ## Default to 5432
## ##
# postgresqlPort: 5432 postgresqlPort: 5432
## PostgreSQL fullname Override
## Default to wiki-postgresql unless fullname override is set for Chart
##
fullnameOverride: ""
## PostgreSQL User to create. ## PostgreSQL User to create.
## ##
postgresqlUser: postgres postgresqlUser: postgres
## PostgreSQL Database to create. ## PostgreSQL Database to create.
## ##
postgresqlDatabase: wiki postgresqlDatabase: wiki
## PostgreSQL password (will be stored in a secret)
##
postgresqlPassword: "postgres"
## Use existing secret for PostgreSQL credentials
## If set, the chart will not create a new secret and will use the existing one
##
# existingSecret: "my-existing-postgres-secret"
## Key in the existing secret containing the password
##
# existingSecretKey: "postgresql-password"
## Key in the existing secret containing the username (defaults to "postgresql-username")
##
# existingSecretUserKey: "postgresql-username"
## Persistent Volume Storage configuration. ## Persistent Volume Storage configuration.
## ref: https://kubernetes.io/docs/user-guide/persistent-volumes ## ref: https://kubernetes.io/docs/user-guide/persistent-volumes
## ##
replication:
## Enable PostgreSQL replication (primary/secondary)
##
enabled: false
persistence: persistence:
## Enable PostgreSQL persistence using Persistent Volume Claims. ## Enable PostgreSQL persistence using Persistent Volume Claims.
## ##
@ -204,3 +219,34 @@ postgresql:
## Persistent Volume Storage Size. ## Persistent Volume Storage Size.
## ##
size: 8Gi size: 8Gi
## PostgreSQL Image Configuration
image:
repository: postgres
tag: "18"
pullPolicy: IfNotPresent
## PostgreSQL Resources Configuration
resources: {}
# We usually recommend not to specify default resources and to leave this as a conscious
# choice for the user. This also increases chances charts run on environments with little
# resources, such as Minikube. If you do want to specify resources, uncomment the following
# lines, adjust them as necessary, and remove the curly braces after 'resources:'.
# limits:
# cpu: 100m
# memory: 128Mi
# requests:
# cpu: 100m
# memory: 128Mi
## PostgreSQL Node Selector, Tolerations and Affinity
nodeSelector: {}
tolerations: []
affinity: {}
## PostgreSQL Service Configuration
service:
type: ClusterIP
port: 5432
# Additional service annotations
annotations: {}

@ -2,10 +2,9 @@
"variables": { "variables": {
"do_api_token": "{{env `DIGITALOCEAN_API_TOKEN`}}", "do_api_token": "{{env `DIGITALOCEAN_API_TOKEN`}}",
"image_name": "wikijs-snapshot-{{timestamp}}", "image_name": "wikijs-snapshot-{{timestamp}}",
"apt_packages": "apt-transport-https ca-certificates curl jq linux-image-extra-virtual software-properties-common gnupg-agent openssl ", "apt_packages": "software-properties-common",
"application_name": "Wiki.js", "application_name": "Wiki.js",
"application_version": "{{env `WIKI_APP_VERSION`}}", "application_version": "{{env `WIKI_APP_VERSION`}}"
"docker_compose_version": "1.29.2"
}, },
"sensitive-variables": [ "sensitive-variables": [
"do_api_token" "do_api_token"
@ -14,7 +13,7 @@
{ {
"type": "digitalocean", "type": "digitalocean",
"api_token": "{{user `do_api_token`}}", "api_token": "{{user `do_api_token`}}",
"image": "ubuntu-20-04-x64", "image": "ubuntu-24-04-x64",
"region": "tor1", "region": "tor1",
"size": "s-1vcpu-1gb", "size": "s-1vcpu-1gb",
"ssh_username": "root", "ssh_username": "root",
@ -73,11 +72,8 @@
], ],
"scripts": [ "scripts": [
"scripts/010-docker.sh", "scripts/010-docker.sh",
"scripts/011-docker-compose.sh", "scripts/011-ufw-docker.sh",
"scripts/012-grub-opts.sh", "scripts/020-force-ssh-logout.sh",
"scripts/013-docker-dns.sh",
"scripts/014-ufw-docker.sh",
"scripts/020-application-tag.sh",
"scripts/900-cleanup.sh", "scripts/900-cleanup.sh",
"scripts/999-img-check.sh" "scripts/999-img-check.sh"
] ]

@ -1,15 +1,18 @@
#!/bin/bash #!/bin/bash
# Scripts in this directory will be executed by cloud-init on the first boot of droplets # Generate PostgreSQL password
# created from your image. Things ike generating passwords, configuration requiring IP address
# or other items that will be unique to each instance should be done in scripts here.
openssl rand -base64 32 > /etc/wiki/.db-secret openssl rand -base64 32 > /etc/wiki/.db-secret
# Start containers
if [[ -z $DATABASE_URL ]]; then if [[ -z $DATABASE_URL ]]; then
docker start db docker start db
fi fi
docker start wiki docker start wiki
docker start wiki-update-companion docker start wiki-update-companion
# docker start nginx-proxy
# docker start watchtower # Remove the ssh force logout command
sed -e '/Match User root/d' \
-e '/.*ForceCommand.*droplet.*/d' \
-i /etc/ssh/sshd_config
systemctl restart ssh

@ -1,17 +1,33 @@
#!/bin/bash #!/bin/bash
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add - # Add Docker's official GPG key:
sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" sudo install -m 0755 -d /etc/apt/keyrings
apt -qqy update sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
apt -qqy -o Dpkg::Options::='--force-confdef' -o Dpkg::Options::='--force-confold' install docker-ce docker-ce-cli containerd.io sudo chmod a+r /etc/apt/keyrings/docker.asc
# Add the repository to Apt sources:
sudo tee /etc/apt/sources.list.d/docker.sources <<EOF
Types: deb
URIs: https://download.docker.com/linux/ubuntu
Suites: $(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}")
Components: stable
Signed-By: /etc/apt/keyrings/docker.asc
EOF
sudo apt -qqy update
# Install Docker
sudo apt -qqy install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
systemctl enable docker systemctl enable docker
systemctl start docker systemctl start docker
# Setup containers
mkdir -p /etc/wiki mkdir -p /etc/wiki
docker network create wikinet docker network create wikinet
docker volume create pgdata docker volume create pgdata
docker create --name=db -e POSTGRES_DB=wiki -e POSTGRES_USER=wiki -e POSTGRES_PASSWORD_FILE=/etc/wiki/.db-secret -v /etc/wiki/.db-secret:/etc/wiki/.db-secret:ro -v pgdata:/var/lib/postgresql/data --restart=unless-stopped -h db --network=wikinet postgres:11 docker create --name=db -e POSTGRES_DB=wiki -e POSTGRES_USER=wiki -e POSTGRES_PASSWORD_FILE=/etc/wiki/.db-secret -v /etc/wiki/.db-secret:/etc/wiki/.db-secret:ro -v pgdata:/var/lib/postgresql/data --restart=unless-stopped -h db --network=wikinet postgres:17
docker create --name=wiki -e DB_TYPE=postgres -e DB_HOST=db -e DB_PORT=5432 -e DB_PASS_FILE=/etc/wiki/.db-secret -v /etc/wiki/.db-secret:/etc/wiki/.db-secret:ro -e DB_USER=wiki -e DB_NAME=wiki -e UPGRADE_COMPANION=1 --restart=unless-stopped -h wiki --network=wikinet -p 80:3000 -p 443:3443 ghcr.io/requarks/wiki:2 docker create --name=wiki -e DB_TYPE=postgres -e DB_HOST=db -e DB_PORT=5432 -e DB_PASS_FILE=/etc/wiki/.db-secret -v /etc/wiki/.db-secret:/etc/wiki/.db-secret:ro -e DB_USER=wiki -e DB_NAME=wiki -e UPGRADE_COMPANION=1 --restart=unless-stopped -h wiki --network=wikinet -p 80:3000 -p 443:3443 ghcr.io/requarks/wiki:2
docker create --name=wiki-update-companion -v /var/run/docker.sock:/var/run/docker.sock:ro --restart=unless-stopped -h wiki-update-companion --network=wikinet ghcr.io/requarks/wiki-update-companion:latest docker create --name=wiki-update-companion -v /var/run/docker.sock:/var/run/docker.sock:ro --restart=unless-stopped -h wiki-update-companion --network=wikinet ghcr.io/requarks/wiki-update-companion:latest

@ -1,4 +0,0 @@
#!/bin/sh
sudo curl -L "https://github.com/docker/compose/releases/download/${docker_compose_version}/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose;
chmod +x /usr/local/bin/docker-compose;

@ -0,0 +1,9 @@
#!/bin/bash
ufw limit ssh
ufw allow http
ufw allow https
ufw --force enable
cat /dev/null > /var/log/ufw.log

@ -1,6 +0,0 @@
#!/bin/sh
sed -e 's|GRUB_CMDLINE_LINUX="|GRUB_CMDLINE_LINUX="cgroup_enable=memory swapaccount=1|g' \
-i /etc/default/grub
update-grub

@ -1,4 +0,0 @@
#!/bin/sh
sed -e 's|#DOCKER_OPTS|DOCKER_OPTS|g' \
-i /etc/default/docker

@ -1,9 +0,0 @@
#!/bin/bash
sudo ufw allow ssh
sudo ufw allow http
sudo ufw allow https
sudo ufw --force enable
cat /dev/null > /var/log/ufw.log

@ -1,24 +0,0 @@
#!/bin/sh
################################
## PART: Write the application tag
##
## vi: syntax=sh expandtab ts=4
build_date=$(date +%Y-%m-%d)
distro="$(lsb_release -s -i)"
distro_release="$(lsb_release -s -r)"
distro_codename="$(lsb_release -s -c)"
distro_arch="$(uname -m)"
mkdir -p /var/lib/digitalocean
cat >> /var/lib/digitalocean/application.info <<EOM
application_name="${application_name}"
build_date="${build_date}"
distro="${distro}"
distro_release="${distro_release}"
distro_codename="${distro_codename}"
distro_arch="${distro_arch}"
application_version="${application_version}"
EOM

@ -0,0 +1,6 @@
#!/bin/sh
cat >> /etc/ssh/sshd_config <<EOM
Match User root
ForceCommand echo "Please wait while we get your droplet ready..."
EOM

@ -5,17 +5,20 @@
myip=$(hostname -I | awk '{print$1}') myip=$(hostname -I | awk '{print$1}')
cat <<EOF cat <<EOF
******************************************************************************** ********************************************************************************
Welcome to Wiki.js's 1-Click DigitalOcean Droplet. Welcome to Wiki.js's 1-Click DigitalOcean Droplet.
To keep this Droplet secure, the UFW firewall is enabled. To keep this Droplet secure, the UFW firewall is enabled.
All ports are BLOCKED except 22 (SSH), 80 (Docker) and 443 (Docker). All ports are BLOCKED except 22 (SSH), 80 (Docker) and 443 (Docker).
* The Wiki.js 1-Click DigitalOcean Quickstart guide is available at: * The Wiki.js 1-Click DigitalOcean Quickstart guide is available at:
https://docs.requarks.io/install/digitalocean https://docs.requarks.io/install/digitalocean
* You can SSH to this Droplet in a terminal as root: ssh root@$myip
* Docker is installed and configured per Docker's recommendations: * Docker is installed and configured per Docker's recommendations:
https://docs.docker.com/install/linux/docker-ce/ubuntu/ https://docs.docker.com/engine/install/ubuntu/
* Docker Compose is installed and configured per Docker's recommendations:
https://docs.docker.com/compose/install/#install-compose
For more information, visit https://docs.requarks.io/install/digitalocean For more information, visit https://docs.requarks.io/install/digitalocean
******************************************************************************** ********************************************************************************
To delete this message of the day: rm -rf $(readlink -f ${0}) To delete this message of the day: rm -rf $(readlink -f ${0})
EOF EOF

@ -1,5 +1,11 @@
#!/bin/bash #!/bin/bash
# DigitalOcean Marketplace Image Validation Tool
# © 2021 DigitalOcean LLC.
# This code is licensed under Apache 2.0 license (see LICENSE.md for details)
set -o errexit
# Ensure /tmp exists and has the proper permissions before # Ensure /tmp exists and has the proper permissions before
# checking for security updates # checking for security updates
# https://github.com/digitalocean/marketplace-partners/issues/94 # https://github.com/digitalocean/marketplace-partners/issues/94
@ -10,11 +16,11 @@ chmod 1777 /tmp
export DEBIAN_FRONTEND=noninteractive export DEBIAN_FRONTEND=noninteractive
apt-get -y update apt-get -y update
apt-get -o Dpkg::Options::="--force-confold" upgrade -q -y --force-yes apt-get -y purge droplet-agent
apt-get purge droplet-agent
rm -rf /opt/digitalocean rm -rf /opt/digitalocean
apt-get -y autoremove apt-get -y autoremove
apt-get -y autoclean apt-get -y autoclean
rm -rf /tmp/* /var/tmp/* rm -rf /tmp/* /var/tmp/*
history -c history -c
cat /dev/null > /root/.bash_history cat /dev/null > /root/.bash_history
@ -36,12 +42,4 @@ The secure erase will complete successfully when you see:${NC}
dd: writing to '/zerofile': No space left on device\n dd: writing to '/zerofile': No space left on device\n
Beginning secure erase now\n" Beginning secure erase now\n"
dd if=/dev/zero of=/zerofile & dd if=/dev/zero of=/zerofile bs=4096 || rm /zerofile
PID=$!
while [ -d /proc/$PID ]
do
printf "."
sleep 5
done
sync; rm /zerofile; sync
cat /dev/null > /var/log/lastlog; cat /dev/null > /var/log/wtmp

@ -4,7 +4,7 @@
# © 2021-2022 DigitalOcean LLC. # © 2021-2022 DigitalOcean LLC.
# This code is licensed under Apache 2.0 license (see LICENSE.md for details) # This code is licensed under Apache 2.0 license (see LICENSE.md for details)
VERSION="v. 1.8" VERSION="v. 1.8.1"
RUNDATE=$( date ) RUNDATE=$( date )
# Script should be run with SUDO # Script should be run with SUDO
@ -75,7 +75,7 @@ function checkAgent {
echo -en "\e[41m[FAIL]\e[0m DigitalOcean directory detected.\n" echo -en "\e[41m[FAIL]\e[0m DigitalOcean directory detected.\n"
((FAIL++)) ((FAIL++))
STATUS=2 STATUS=2
if [[ $OS == "CentOS Linux" ]] || [[ $OS == "CentOS Stream" ]] || [[ $OS == "Rocky Linux" ]]; then if [[ $OS == "CentOS Linux" ]] || [[ $OS == "CentOS Stream" ]] || [[ $OS == "Rocky Linux" ]] || [[ $OS == "AlmaLinux" ]] || [[ $OS == "CloudLinux" ]]; then
echo "To uninstall the agent: 'sudo yum remove droplet-agent'" echo "To uninstall the agent: 'sudo yum remove droplet-agent'"
echo "To remove the DO directory: 'find /opt/digitalocean/ -type d -empty -delete'" echo "To remove the DO directory: 'find /opt/digitalocean/ -type d -empty -delete'"
elif [[ $OS == "Ubuntu" ]] || [[ $OS == "Debian" ]]; then elif [[ $OS == "Ubuntu" ]] || [[ $OS == "Debian" ]]; then
@ -357,7 +357,7 @@ function checkFirewall {
# shellcheck disable=SC2031 # shellcheck disable=SC2031
((WARN++)) ((WARN++))
fi fi
elif [[ $OS == "CentOS Linux" ]] || [[ $OS == "CentOS Stream" ]] || [[ $OS == "Rocky Linux" ]]; then elif [[ $OS == "CentOS Linux" ]] || [[ $OS == "CentOS Stream" ]] || [[ $OS == "Rocky Linux" ]] || [[ $OS == "AlmaLinux" ]] || [[ $OS == "CloudLinux" ]]; then
if [ -f /usr/lib/systemd/system/csf.service ]; then if [ -f /usr/lib/systemd/system/csf.service ]; then
fw="csf" fw="csf"
if [[ $(systemctl status $fw >/dev/null 2>&1) ]]; then if [[ $(systemctl status $fw >/dev/null 2>&1) ]]; then
@ -456,7 +456,7 @@ function checkUpdates {
echo -en "\e[32m[PASS]\e[0m There are no pending security updates for this image.\n\n" echo -en "\e[32m[PASS]\e[0m There are no pending security updates for this image.\n\n"
((PASS++)) ((PASS++))
fi fi
elif [[ $OS == "CentOS Linux" ]] || [[ $OS == "CentOS Stream" ]] || [[ $OS == "Rocky Linux" ]]; then elif [[ $OS == "CentOS Linux" ]] || [[ $OS == "CentOS Stream" ]] || [[ $OS == "Rocky Linux" ]] || [[ $OS == "AlmaLinux" ]] || [[ $OS == "CloudLinux" ]]; then
echo -en "\nChecking for available security updates, this may take a minute...\n\n" echo -en "\nChecking for available security updates, this may take a minute...\n\n"
update_count=$(yum check-update --security --quiet | wc -l) update_count=$(yum check-update --security --quiet | wc -l)
@ -506,7 +506,7 @@ osv=0
if [[ $OS == "Ubuntu" ]]; then if [[ $OS == "Ubuntu" ]]; then
ost=1 ost=1
if [[ $VER == "22.04" ]] || [[ $VER == "20.04" ]] || [[ $VER == "18.04" ]] || [[ $VER == "16.04" ]]; then if [[ $VER == "24.04" ]] || [[ $VER == "22.10" ]] || [[ $VER == "22.04" ]] || [[ $VER == "20.04" ]] || [[ $VER == "18.04" ]] || [[ $VER == "16.04" ]]; then
osv=1 osv=1
fi fi
@ -522,6 +522,12 @@ elif [[ "$OS" =~ Debian.* ]]; then
11) 11)
osv=1 osv=1
;; ;;
12)
osv=1
;;
13)
osv=1
;;
*) *)
osv=2 osv=2
;; ;;
@ -542,12 +548,28 @@ elif [[ $OS == "CentOS Stream" ]]; then
ost=1 ost=1
if [[ $VER == "8" ]]; then if [[ $VER == "8" ]]; then
osv=1 osv=1
elif [[ $VER == "9" ]]; then
osv=1
else else
osv=2 osv=2
fi fi
elif [[ $OS == "Rocky Linux" ]]; then elif [[ $OS == "Rocky Linux" ]]; then
ost=1 ost=1
if [[ $VER =~ 8\. ]]; then if [[ $VER =~ 8\. ]] || [[ $VER =~ 9\. ]]; then
osv=1
else
osv=2
fi
elif [[ $OS == "AlmaLinux" ]]; then
ost=1
if [[ "$VER" =~ 8.* ]] || [[ "$VER" =~ 9.* ]]; then
osv=1
else
osv=2
fi
elif [[ $OS == "CloudLinux" ]]; then
ost=1
if [[ "$VER" =~ 8.* ]] || [[ "$VER" =~ 9.* ]]; then
osv=1 osv=1
else else
osv=2 osv=2
@ -599,6 +621,12 @@ checkRoot
checkAgent checkAgent
# Source GPU compatibility check
if [ -f "$(dirname "$0")/check_gpu_support.sh" ]; then
source "$(dirname "$0")/check_gpu_support.sh"
else
echo "GPU check script not found. Skipping GPU compatibility checks."
fi
# Summary # Summary
echo -en "\n\n---------------------------------------------------------------------------------------------------\n" echo -en "\n\n---------------------------------------------------------------------------------------------------\n"

@ -1,15 +1,15 @@
{ {
"name": "wiki", "name": "wiki",
"version": "2.0.0", "version": "2.0.0",
"releaseDate": "2019-01-01T01:01:01.000Z", "releaseDate": "2026-01-01T01:01:01.000Z",
"description": "A modern, lightweight and powerful wiki app built on NodeJS, Git and Markdown", "description": "A modern, lightweight and powerful wiki app built on NodeJS, Git and Markdown",
"main": "wiki.js", "main": "wiki.js",
"dev": true, "dev": true,
"scripts": { "scripts": {
"start": "node server", "start": "node server",
"dev": "NODE_OPTIONS=--openssl-legacy-provider node dev", "dev": "cross-env NODE_OPTIONS=--openssl-legacy-provider node dev",
"build": "NODE_OPTIONS=--openssl-legacy-provider webpack --profile --config dev/webpack/webpack.prod.js", "build": "cross-env NODE_OPTIONS=--openssl-legacy-provider webpack --profile --config dev/webpack/webpack.prod.js",
"watch": "NODE_OPTIONS=--openssl-legacy-provider webpack --config dev/webpack/webpack.dev.js", "watch": "cross-env NODE_OPTIONS=--openssl-legacy-provider webpack --config dev/webpack/webpack.dev.js",
"test": "eslint --format codeframe --ext .js,.vue . && pug-lint server/views && jest", "test": "eslint --format codeframe --ext .js,.vue . && pug-lint server/views && jest",
"cypress:open": "cypress open", "cypress:open": "cypress open",
"postinstall": "patch-package" "postinstall": "patch-package"
@ -34,10 +34,10 @@
}, },
"homepage": "https://github.com/Requarks/wiki#readme", "homepage": "https://github.com/Requarks/wiki#readme",
"engines": { "engines": {
"node": ">=10.12" "node": ">=20"
}, },
"dependencies": { "dependencies": {
"@azure/storage-blob": "12.12.0", "@azure/storage-blob": "12.29.1",
"@exlinc/keycloak-passport": "1.0.2", "@exlinc/keycloak-passport": "1.0.2",
"@joplin/turndown-plugin-gfm": "1.0.45", "@joplin/turndown-plugin-gfm": "1.0.45",
"@root/csr": "0.8.1", "@root/csr": "0.8.1",
@ -51,7 +51,7 @@
"apollo-server-express": "2.25.2", "apollo-server-express": "2.25.2",
"asciidoctor": "2.2.6", "asciidoctor": "2.2.6",
"auto-load": "3.0.4", "auto-load": "3.0.4",
"aws-sdk": "2.1309.0", "aws-sdk": "2.1693.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",
@ -60,18 +60,18 @@
"cheerio": "1.0.0-rc.5", "cheerio": "1.0.0-rc.5",
"chokidar": "3.5.3", "chokidar": "3.5.3",
"chromium-pickle-js": "0.2.0", "chromium-pickle-js": "0.2.0",
"clean-css": "5.3.2", "clean-css": "5.3.3",
"command-exists": "1.2.9", "command-exists": "1.2.9",
"compression": "1.7.4", "compression": "1.8.1",
"connect-session-knex": "2.0.0", "connect-session-knex": "2.0.0",
"cookie-parser": "1.4.6", "cookie-parser": "1.4.7",
"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",
"dependency-graph": "0.11.0", "dependency-graph": "0.11.0",
"diff": "4.0.2", "diff": "4.0.2",
"diff2html": "3.1.14", "diff2html": "3.1.14",
"dompurify": "2.4.3", "dompurify": "3.3.1",
"dotize": "0.3.0", "dotize": "0.3.0",
"elasticsearch6": "npm:@elastic/elasticsearch@6", "elasticsearch6": "npm:@elastic/elasticsearch@6",
"elasticsearch7": "npm:@elastic/elasticsearch@7", "elasticsearch7": "npm:@elastic/elasticsearch@7",
@ -80,7 +80,7 @@
"eventemitter2": "6.4.9", "eventemitter2": "6.4.9",
"express": "4.18.2", "express": "4.18.2",
"express-brute": "1.0.1", "express-brute": "1.0.1",
"express-session": "1.17.3", "express-session": "1.18.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",
@ -96,11 +96,11 @@
"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.7.4", "js-base64": "3.7.8",
"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",
"jsonwebtoken": "9.0.0", "jsonwebtoken": "9.0.3",
"katex": "0.12.0", "katex": "0.12.0",
"klaw": "3.0.0", "klaw": "3.0.0",
"knex": "0.21.7", "knex": "0.21.7",
@ -124,21 +124,21 @@
"markdown-it-task-lists": "2.1.1", "markdown-it-task-lists": "2.1.1",
"mathjax": "3.2.2", "mathjax": "3.2.2",
"mime-types": "2.1.35", "mime-types": "2.1.35",
"moment": "2.29.4", "moment": "2.30.1",
"moment-timezone": "0.5.40", "moment-timezone": "0.6.0",
"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.4", "multer": "1.4.4",
"mysql2": "3.1.0", "mysql2": "3.16.0",
"nanoid": "3.2.0", "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.9.1", "nodemailer": "6.9.1",
"objection": "2.2.18", "objection": "2.2.18",
"passport": "0.4.1", "passport": "0.4.1",
"passport-auth0": "1.4.3", "passport-auth0": "1.4.5",
"passport-azure-ad": "4.3.4", "passport-azure-ad": "4.3.5",
"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",
@ -150,47 +150,47 @@
"passport-ldapauth": "3.0.1", "passport-ldapauth": "3.0.1",
"passport-local": "1.0.0", "passport-local": "1.0.0",
"passport-microsoft": "0.1.0", "passport-microsoft": "0.1.0",
"passport-oauth2": "1.6.1", "passport-oauth2": "1.8.0",
"passport-okta-oauth": "0.0.1", "passport-okta-oauth": "0.0.1",
"passport-openidconnect": "0.1.1", "passport-openidconnect": "0.1.2",
"passport-saml": "3.2.4", "passport-saml": "3.2.4",
"passport-slack-oauth2": "1.1.1", "passport-slack-oauth2": "1.2.0",
"passport-twitch-strategy": "2.2.0", "passport-twitch-strategy": "2.2.0",
"patch-package": "8.0.0", "patch-package": "8.0.1",
"pem-jwk": "2.0.0", "pem-jwk": "2.0.0",
"pg": "8.9.0", "pg": "8.16.3",
"pg-hstore": "2.3.4", "pg-hstore": "2.3.4",
"pg-pubsub": "0.5.0", "pg-pubsub": "0.8.1",
"pg-query-stream": "4.3.0", "pg-query-stream": "4.10.3",
"pg-tsquery": "8.4.1", "pg-tsquery": "8.4.2",
"postinstall-postinstall": "2.1.0", "postinstall-postinstall": "2.1.0",
"pug": "3.0.2", "pug": "3.0.3",
"punycode": "2.3.0", "punycode": "2.3.1",
"qr-image": "3.2.0", "qr-image": "3.2.0",
"raven": "2.6.4", "raven": "2.6.4",
"remove-markdown": "0.5.0", "remove-markdown": "0.6.2",
"request": "2.88.2", "request": "2.88.2",
"request-promise": "4.2.6", "request-promise": "4.2.6",
"safe-regex": "2.1.1", "safe-regex": "2.1.1",
"sanitize-filename": "1.6.3", "sanitize-filename": "1.6.3",
"scim-query-filter-parser": "2.0.4", "scim-query-filter-parser": "2.0.4",
"semver": "7.3.8", "semver": "7.7.3",
"serve-favicon": "2.5.0", "serve-favicon": "2.5.1",
"simple-git": "3.16.0", "simple-git": "3.30.0",
"solr-node": "1.2.1", "solr-node": "1.2.1",
"sqlite3": "5.1.4", "sqlite3": "5.1.7",
"ssh2": "1.11.0", "ssh2": "1.11.0",
"ssh2-promise": "1.0.3", "ssh2-promise": "1.0.3",
"striptags": "3.2.0", "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.1.1", "turndown": "7.2.2",
"twemoji": "14.0.2", "twemoji": "14.0.2",
"uslug": "1.0.4", "uslug": "1.0.4",
"uuid": "9.0.0", "uuid": "9.0.0",
"validate.js": "0.13.1", "validate.js": "0.13.1",
"winston": "3.8.2", "winston": "3.8.2",
"xss": "1.0.14", "xss": "1.0.15",
"yargs": "17.6.2" "yargs": "17.6.2"
}, },
"devDependencies": { "devDependencies": {
@ -240,6 +240,7 @@
"codemirror-asciidoc": "1.0.4", "codemirror-asciidoc": "1.0.4",
"copy-webpack-plugin": "6.2.1", "copy-webpack-plugin": "6.2.1",
"core-js": "3.6.5", "core-js": "3.6.5",
"cross-env": "10.0.0",
"css-loader": "4.3.0", "css-loader": "4.3.0",
"cssnano": "4.1.10", "cssnano": "4.1.10",
"cypress": "5.3.0", "cypress": "5.3.0",

@ -4,8 +4,8 @@ const express = require('express')
const ExpressBrute = require('express-brute') const ExpressBrute = require('express-brute')
const BruteKnex = require('../helpers/brute-knex') const BruteKnex = require('../helpers/brute-knex')
const router = express.Router() const router = express.Router()
const moment = require('moment')
const _ = require('lodash') const _ = require('lodash')
const commonHelper = require('../helpers/common')
const bruteforce = new ExpressBrute(new BruteKnex({ const bruteforce = new ExpressBrute(new BruteKnex({
createTable: true, createTable: true,
@ -70,19 +70,25 @@ router.all('/login/:strategy/callback', async (req, res, next) => {
const authResult = await WIKI.models.users.login({ const authResult = await WIKI.models.users.login({
strategy: req.params.strategy strategy: req.params.strategy
}, { req, res }) }, { req, res })
res.cookie('jwt', authResult.jwt, { expires: moment().add(1, 'y').toDate() }) res.cookie('jwt', authResult.jwt, commonHelper.getCookieOpts())
const loginRedirect = req.cookies['loginRedirect'] const loginRedirect = req.cookies['loginRedirect']
const isValidRedirect = loginRedirect && loginRedirect.startsWith('/') && !loginRedirect.startsWith('//') && !loginRedirect.includes('://')
if (loginRedirect === '/' && authResult.redirect) { if (loginRedirect === '/' && authResult.redirect) {
res.clearCookie('loginRedirect') res.clearCookie('loginRedirect')
res.redirect(authResult.redirect) res.redirect(authResult.redirect)
} else if (loginRedirect) { } else if (isValidRedirect) {
res.clearCookie('loginRedirect') res.clearCookie('loginRedirect')
res.redirect(loginRedirect) res.redirect(loginRedirect)
} else if (authResult.redirect) {
res.redirect(authResult.redirect)
} else { } else {
res.redirect('/') if (loginRedirect) {
res.clearCookie('loginRedirect')
}
if (authResult.redirect) {
res.redirect(authResult.redirect)
} else {
res.redirect('/')
}
} }
} catch (err) { } catch (err) {
next(err) next(err)
@ -94,8 +100,7 @@ router.all('/login/:strategy/callback', async (req, res, next) => {
*/ */
router.post('/login', bruteforce.prevent, async (req, res, next) => { router.post('/login', bruteforce.prevent, async (req, res, next) => {
_.set(res.locals, 'pageMeta.title', 'Login') _.set(res.locals, 'pageMeta.title', 'Login')
if (req.query.legacy || (req.get('user-agent') && req.get('user-agent').indexOf('Trident') >= 0)) {
if (req.query.legacy || req.get('user-agent').indexOf('Trident') >= 0) {
try { try {
const authResult = await WIKI.models.users.login({ const authResult = await WIKI.models.users.login({
strategy: req.body.strategy, strategy: req.body.strategy,
@ -103,7 +108,7 @@ router.post('/login', bruteforce.prevent, async (req, res, next) => {
password: req.body.pass password: req.body.pass
}, { req, res }) }, { req, res })
req.brute.reset() req.brute.reset()
res.cookie('jwt', authResult.jwt, { expires: moment().add(1, 'y').toDate() }) res.cookie('jwt', authResult.jwt, commonHelper.getCookieOpts())
res.redirect('/') res.redirect('/')
} catch (err) { } catch (err) {
const { formStrategies, socialStrategies } = await WIKI.models.authentication.getStrategiesForLegacyClient() const { formStrategies, socialStrategies } = await WIKI.models.authentication.getStrategiesForLegacyClient()
@ -153,7 +158,7 @@ router.get('/verify/:token', bruteforce.prevent, async (req, res, next) => {
res.redirect('/login') res.redirect('/login')
} else { } else {
const result = await WIKI.models.users.refreshToken(usr) const result = await WIKI.models.users.refreshToken(usr)
res.cookie('jwt', result.token, { expires: moment().add(1, 'years').toDate() }) res.cookie('jwt', result.token, commonHelper.getCookieOpts())
res.redirect('/') res.redirect('/')
} }
} catch (err) { } catch (err) {

@ -75,12 +75,12 @@ router.get(['/d', '/d/*'], async (req, res, next) => {
if (versionId > 0) { if (versionId > 0) {
if (!WIKI.auth.checkAccess(req.user, ['read:history'], pageArgs)) { if (!WIKI.auth.checkAccess(req.user, ['read:history'], pageArgs)) {
_.set(res.locals, 'pageMeta.title', 'Unauthorized') _.set(res.locals, 'pageMeta.title', 'Unauthorized')
return res.render('unauthorized', { action: 'downloadVersion' }) return res.status(403).render('unauthorized', { action: 'downloadVersion' })
} }
} else { } else {
if (!WIKI.auth.checkAccess(req.user, ['read:source'], pageArgs)) { if (!WIKI.auth.checkAccess(req.user, ['read:source'], pageArgs)) {
_.set(res.locals, 'pageMeta.title', 'Unauthorized') _.set(res.locals, 'pageMeta.title', 'Unauthorized')
return res.render('unauthorized', { action: 'download' }) return res.status(403).render('unauthorized', { action: 'download' })
} }
} }
@ -142,7 +142,7 @@ router.get(['/e', '/e/*'], async (req, res, next) => {
// -> EDIT MODE // -> EDIT MODE
if (!(effectivePermissions.pages.write || effectivePermissions.pages.manage)) { if (!(effectivePermissions.pages.write || effectivePermissions.pages.manage)) {
_.set(res.locals, 'pageMeta.title', 'Unauthorized') _.set(res.locals, 'pageMeta.title', 'Unauthorized')
return res.render('unauthorized', { action: 'edit' }) return res.status(403).render('unauthorized', { action: 'edit' })
} }
// -> Get page tags // -> Get page tags
@ -166,7 +166,7 @@ router.get(['/e', '/e/*'], async (req, res, next) => {
// -> CREATE MODE // -> CREATE MODE
if (!effectivePermissions.pages.write) { if (!effectivePermissions.pages.write) {
_.set(res.locals, 'pageMeta.title', 'Unauthorized') _.set(res.locals, 'pageMeta.title', 'Unauthorized')
return res.render('unauthorized', { action: 'create' }) return res.status(403).render('unauthorized', { action: 'create' })
} }
_.set(res.locals, 'pageMeta.title', `New Page`) _.set(res.locals, 'pageMeta.title', `New Page`)
@ -206,7 +206,7 @@ router.get(['/e', '/e/*'], async (req, res, next) => {
} }
if (!WIKI.auth.checkAccess(req.user, ['read:history'], { path: pageVersion.path, locale: pageVersion.locale })) { if (!WIKI.auth.checkAccess(req.user, ['read:history'], { path: pageVersion.path, locale: pageVersion.locale })) {
_.set(res.locals, 'pageMeta.title', 'Unauthorized') _.set(res.locals, 'pageMeta.title', 'Unauthorized')
return res.render('unauthorized', { action: 'sourceVersion' }) return res.status(403).render('unauthorized', { action: 'sourceVersion' })
} }
page.content = Buffer.from(pageVersion.content).toString('base64') page.content = Buffer.from(pageVersion.content).toString('base64')
page.editorKey = pageVersion.editor page.editorKey = pageVersion.editor
@ -221,7 +221,7 @@ router.get(['/e', '/e/*'], async (req, res, next) => {
} }
if (!WIKI.auth.checkAccess(req.user, ['read:source'], { path: pageOriginal.path, locale: pageOriginal.locale })) { if (!WIKI.auth.checkAccess(req.user, ['read:source'], { path: pageOriginal.path, locale: pageOriginal.locale })) {
_.set(res.locals, 'pageMeta.title', 'Unauthorized') _.set(res.locals, 'pageMeta.title', 'Unauthorized')
return res.render('unauthorized', { action: 'source' }) return res.status(403).render('unauthorized', { action: 'source' })
} }
page.content = Buffer.from(pageOriginal.content).toString('base64') page.content = Buffer.from(pageOriginal.content).toString('base64')
page.editorKey = pageOriginal.editorKey page.editorKey = pageOriginal.editorKey
@ -304,7 +304,7 @@ router.get(['/i', '/i/:id'], async (req, res, next) => {
tags: page.tags tags: page.tags
})) { })) {
_.set(res.locals, 'pageMeta.title', 'Unauthorized') _.set(res.locals, 'pageMeta.title', 'Unauthorized')
return res.render('unauthorized', { action: 'view' }) return res.status(403).render('unauthorized', { action: 'view' })
} }
if (WIKI.config.lang.namespacing) { if (WIKI.config.lang.namespacing) {
@ -319,7 +319,7 @@ router.get(['/i', '/i/:id'], async (req, res, next) => {
*/ */
router.get(['/p', '/p/*'], (req, res, next) => { router.get(['/p', '/p/*'], (req, res, next) => {
if (!req.user || req.user.id < 1 || req.user.id === 2) { if (!req.user || req.user.id < 1 || req.user.id === 2) {
return res.render('unauthorized', { action: 'view' }) return res.status(403).render('unauthorized', { action: 'view' })
} }
_.set(res.locals, 'pageMeta.title', 'User Profile') _.set(res.locals, 'pageMeta.title', 'User Profile')
@ -355,12 +355,12 @@ router.get(['/s', '/s/*'], async (req, res, next) => {
if (versionId > 0) { if (versionId > 0) {
if (!effectivePermissions.history.read) { if (!effectivePermissions.history.read) {
_.set(res.locals, 'pageMeta.title', 'Unauthorized') _.set(res.locals, 'pageMeta.title', 'Unauthorized')
return res.render('unauthorized', { action: 'sourceVersion' }) return res.status(403).render('unauthorized', { action: 'sourceVersion' })
} }
} else { } else {
if (!effectivePermissions.source.read) { if (!effectivePermissions.source.read) {
_.set(res.locals, 'pageMeta.title', 'Unauthorized') _.set(res.locals, 'pageMeta.title', 'Unauthorized')
return res.render('unauthorized', { action: 'source' }) return res.status(403).render('unauthorized', { action: 'source' })
} }
} }
@ -507,7 +507,7 @@ router.get('/*', async (req, res, next) => {
injectCode.body = `${injectCode.body}\n${page.extra.js}` injectCode.body = `${injectCode.body}\n${page.extra.js}`
} }
if (req.query.legacy || req.get('user-agent').indexOf('Trident') >= 0) { if (req.query.legacy || (req.get('user-agent') && req.get('user-agent').indexOf('Trident') >= 0)) {
// -> Convert page TOC // -> Convert page TOC
if (_.isString(page.toc)) { if (_.isString(page.toc)) {
page.toc = JSON.parse(page.toc) page.toc = JSON.parse(page.toc)

@ -4,10 +4,11 @@ const _ = require('lodash')
const jwt = require('jsonwebtoken') const jwt = require('jsonwebtoken')
const ms = require('ms') const ms = require('ms')
const { DateTime } = require('luxon') const { DateTime } = require('luxon')
const Promise = require('bluebird') const crypto = require('crypto')
const crypto = Promise.promisifyAll(require('crypto'))
const pem2jwk = require('pem-jwk').pem2jwk const pem2jwk = require('pem-jwk').pem2jwk
const randomBytesAsync = require('util').promisify(crypto.randomBytes)
const commonHelper = require('../helpers/common')
const securityHelper = require('../helpers/security') const securityHelper = require('../helpers/security')
/* global WIKI */ /* global WIKI */
@ -82,7 +83,7 @@ module.exports = {
const strategy = require(`../modules/authentication/${stg.strategyKey}/authentication.js`) const strategy = require(`../modules/authentication/${stg.strategyKey}/authentication.js`)
stg.config.callbackURL = `${WIKI.config.host}/login/${stg.key}/callback` stg.config.callbackURL = `${WIKI.config.host}/login/${stg.key}/callback`
stg.config.key = stg.key; stg.config.key = stg.key
strategy.init(passport, stg.config) strategy.init(passport, stg.config)
strategy.config = stg.config strategy.config = stg.config
@ -154,7 +155,7 @@ module.exports = {
if (req.get('content-type') === 'application/json') { if (req.get('content-type') === 'application/json') {
res.set('new-jwt', newToken.token) res.set('new-jwt', newToken.token)
} else { } else {
res.cookie('jwt', newToken.token, { expires: DateTime.utc().plus({ days: 365 }).toJSDate() }) res.cookie('jwt', newToken.token, commonHelper.getCookieOpts())
} }
// Avoid caching this response // Avoid caching this response
@ -316,6 +317,49 @@ module.exports = {
return true return true
}, },
/**
* Check if user (requester) can perform user assignment to a group with elevated permissions
*
* @param {User} requester The user attempting to perform the assignment
* @param {Array<Number>} groupIds List of group IDs to be assigned
* @returns {Boolean}
*/
async checkAssignUserToGroupAccess(requester, groupIds = []) {
if (!groupIds || groupIds.length < 1) {
return true
}
const requesterPermissions = requester.permissions ? requester.permissions : requester.getGlobalPermissions()
// System Admin
if (requesterPermissions.includes('manage:system')) {
return true
}
// Ensure basic user management permission
if (!requesterPermissions.some(p => ['write:users', 'manage:users', 'write:groups', 'manage:groups'].includes(p))) {
return false
}
const groups = await WIKI.models.groups.query().whereIn('id', groupIds)
return groups.every(grp => {
// Check group for manage:system permission
if (grp.permissions.includes('manage:system')) {
return false
}
// Check group for administrative permissions
if (grp.permissions.some(p => {
const permType = _.last(p.split(':'))
return ['users', 'groups', 'navigation', 'theme', 'api'].includes(permType)
}) && !requesterPermissions.includes('manage:groups')) {
return false
}
return true
})
},
/** /**
* Check and apply Page Rule specificity * Check and apply Page Rule specificity
* *
@ -366,7 +410,7 @@ module.exports = {
async regenerateCertificates () { async regenerateCertificates () {
WIKI.logger.info('Regenerating certificates...') WIKI.logger.info('Regenerating certificates...')
_.set(WIKI.config, 'sessionSecret', (await crypto.randomBytesAsync(32)).toString('hex')) _.set(WIKI.config, 'sessionSecret', (await randomBytesAsync(32)).toString('hex'))
const certs = crypto.generateKeyPairSync('rsa', { const certs = crypto.generateKeyPairSync('rsa', {
modulusLength: 2048, modulusLength: 2048,
publicKeyEncoding: { publicKeyEncoding: {

@ -91,6 +91,13 @@ module.exports = {
dbConfig.ssl = sslOptions dbConfig.ssl = sslOptions
} }
// Prune host and port if socketPath is configured
if (WIKI.config.db.socketPath) {
const { host, port, ...prunedConfig } = dbConfig
dbConfig = prunedConfig
dbConfig.socketPath = WIKI.config.db.socketPath.toString()
}
// Fix mysql boolean handling... // Fix mysql boolean handling...
dbConfig.typeCast = (field, next) => { dbConfig.typeCast = (field, next) => {
if (field.type === 'TINY' && field.length === 1) { if (field.type === 'TINY' && field.length === 1) {

@ -4,6 +4,8 @@ const https = require('https')
const { ApolloServer } = require('apollo-server-express') const { ApolloServer } = require('apollo-server-express')
const Promise = require('bluebird') const Promise = require('bluebird')
const _ = require('lodash') const _ = require('lodash')
const jwt = require('jsonwebtoken')
const cookie = require('cookie')
/* global WIKI */ /* global WIKI */
@ -125,7 +127,35 @@ module.exports = {
context: ({ req, res }) => ({ req, res }), context: ({ req, res }) => ({ req, res }),
subscriptions: { subscriptions: {
onConnect: (connectionParams, webSocket) => { onConnect: (connectionParams, webSocket) => {
let token = _.get(connectionParams, 'token', null)
if (!token) {
const cookieHeader = _.get(webSocket, 'upgradeReq.headers.cookie', '')
if (cookieHeader) {
const cookies = cookie.parse(cookieHeader)
token = cookies.jwt || null
}
}
if (!token) {
throw new Error('Unauthorized')
}
try {
const user = jwt.verify(token, WIKI.config.certs.public, {
audience: WIKI.config.auth.audience,
issuer: 'urn:wiki.js',
algorithms: ['RS256']
})
if (!_.includes(user.permissions, 'manage:system')) {
throw new Error('Forbidden')
}
return { user }
} catch (err) {
throw new Error('Unauthorized')
}
}, },
path: '/graphql-subscriptions' path: '/graphql-subscriptions'
} }

@ -4,8 +4,8 @@ const Promise = require('bluebird')
const fs = require('fs-extra') const fs = require('fs-extra')
const path = require('path') const path = require('path')
const zlib = require('zlib') const zlib = require('zlib')
const stream = require('stream') const { pipeline } = require('node:stream/promises')
const pipeline = Promise.promisify(stream.pipeline) const { Readable, Transform } = require('node:stream')
/* global WIKI */ /* global WIKI */
@ -121,7 +121,7 @@ module.exports = {
await pipeline( await pipeline(
WIKI.models.knex.select('filename', 'folderId', 'data').from('assets').join('assetData', 'assets.id', '=', 'assetData.id').stream(), WIKI.models.knex.select('filename', 'folderId', 'data').from('assets').join('assetData', 'assets.id', '=', 'assetData.id').stream(),
new stream.Transform({ new Transform({
objectMode: true, objectMode: true,
transform: async (asset, enc, cb) => { transform: async (asset, enc, cb) => {
const filename = (asset.folderId && asset.folderId > 0) ? `${_.get(assetFolders, asset.folderId)}/${asset.filename}` : asset.filename const filename = (asset.folderId && asset.folderId > 0) ? `${_.get(assetFolders, asset.folderId)}/${asset.filename}` : asset.filename
@ -150,7 +150,7 @@ module.exports = {
const commentsProgressMultiplier = progressMultiplier / Math.ceil(commentsCount / 50) const commentsProgressMultiplier = progressMultiplier / Math.ceil(commentsCount / 50)
WIKI.logger.info(`Found ${commentsCount} comments to export. Streaming to file...`) WIKI.logger.info(`Found ${commentsCount} comments to export. Streaming to file...`)
const rs = stream.Readable({ objectMode: true }) const rs = Readable({ objectMode: true })
rs._read = () => {} rs._read = () => {}
const fetchCommentsBatch = async (offset) => { const fetchCommentsBatch = async (offset) => {
@ -177,7 +177,7 @@ module.exports = {
let marker = 0 let marker = 0
await pipeline( await pipeline(
rs, rs,
new stream.Transform({ new Transform({
objectMode: true, objectMode: true,
transform (chunk, encoding, callback) { transform (chunk, encoding, callback) {
marker++ marker++
@ -225,7 +225,7 @@ module.exports = {
const pagesProgressMultiplier = progressMultiplier / Math.ceil(pagesCount / 10) const pagesProgressMultiplier = progressMultiplier / Math.ceil(pagesCount / 10)
WIKI.logger.info(`Found ${pagesCount} pages history to export. Streaming to file...`) WIKI.logger.info(`Found ${pagesCount} pages history to export. Streaming to file...`)
const rs = stream.Readable({ objectMode: true }) const rs = Readable({ objectMode: true })
rs._read = () => {} rs._read = () => {}
const fetchPagesBatch = async (offset) => { const fetchPagesBatch = async (offset) => {
@ -255,7 +255,7 @@ module.exports = {
let marker = 0 let marker = 0
await pipeline( await pipeline(
rs, rs,
new stream.Transform({ new Transform({
objectMode: true, objectMode: true,
transform (chunk, encoding, callback) { transform (chunk, encoding, callback) {
marker++ marker++
@ -307,7 +307,7 @@ module.exports = {
const pagesProgressMultiplier = progressMultiplier / Math.ceil(pagesCount / 10) const pagesProgressMultiplier = progressMultiplier / Math.ceil(pagesCount / 10)
WIKI.logger.info(`Found ${pagesCount} pages to export. Streaming to file...`) WIKI.logger.info(`Found ${pagesCount} pages to export. Streaming to file...`)
const rs = stream.Readable({ objectMode: true }) const rs = Readable({ objectMode: true })
rs._read = () => {} rs._read = () => {}
const fetchPagesBatch = async (offset) => { const fetchPagesBatch = async (offset) => {
@ -337,7 +337,7 @@ module.exports = {
let marker = 0 let marker = 0
await pipeline( await pipeline(
rs, rs,
new stream.Transform({ new Transform({
objectMode: true, objectMode: true,
transform (chunk, encoding, callback) { transform (chunk, encoding, callback) {
marker++ marker++
@ -400,7 +400,7 @@ module.exports = {
const usersProgressMultiplier = progressMultiplier / Math.ceil(usersCount / 50) const usersProgressMultiplier = progressMultiplier / Math.ceil(usersCount / 50)
WIKI.logger.info(`Found ${usersCount} users to export. Streaming to file...`) WIKI.logger.info(`Found ${usersCount} users to export. Streaming to file...`)
const rs = stream.Readable({ objectMode: true }) const rs = Readable({ objectMode: true })
rs._read = () => {} rs._read = () => {}
const fetchUsersBatch = async (offset) => { const fetchUsersBatch = async (offset) => {
@ -427,7 +427,7 @@ module.exports = {
let marker = 0 let marker = 0
await pipeline( await pipeline(
rs, rs,
new stream.Transform({ new Transform({
objectMode: true, objectMode: true,
transform (chunk, encoding, callback) { transform (chunk, encoding, callback) {
marker++ marker++

@ -45,15 +45,23 @@ module.exports = {
throw new gql.GraphQLError('Invalid Group ID') throw new gql.GraphQLError('Invalid Group ID')
} }
// Check assigned permissions for write:groups // Check assigned permissions for manage:users / write:groups
if ( if (
WIKI.auth.checkExclusiveAccess(req.user, ['write:groups'], ['manage:groups', 'manage:system']) && WIKI.auth.checkExclusiveAccess(req.user, ['manage:users', 'write:groups'], ['manage:groups', 'manage:system']) &&
grp.permissions.some(p => { grp.permissions.some(p => {
const resType = _.last(p.split(':')) const resType = _.last(p.split(':'))
return ['users', 'groups', 'navigation', 'theme', 'api', 'system'].includes(resType) return ['users', 'groups', 'navigation', 'theme', 'api', 'system'].includes(resType)
}) })
) { ) {
throw new gql.GraphQLError('You are not authorized to assign a user to this elevated group.') throw new gql.GraphQLError('You are not authorized to assign a user to this administrative group.')
}
// Check assigned permissions for manage:groups
if (
WIKI.auth.checkExclusiveAccess(req.user, ['manage:groups'], ['manage:system']) &&
grp.permissions.some(p => _.last(p.split(':')) === 'system')
) {
throw new gql.GraphQLError('You are not authorized to assign a user to a group with the manage:system permission.')
} }
// Check for valid user // Check for valid user
@ -170,7 +178,7 @@ module.exports = {
return ['users', 'groups', 'navigation', 'theme', 'api', 'system'].includes(resType) return ['users', 'groups', 'navigation', 'theme', 'api', 'system'].includes(resType)
}) })
) { ) {
throw new gql.GraphQLError('You are not authorized to manage this group or assign these permissions.') throw new gql.GraphQLError('You are not authorized to manage this group or assign these administrative permissions.')
} }
// Check assigned permissions for manage:groups // Check assigned permissions for manage:groups

@ -1,6 +1,5 @@
const _ = require('lodash') const _ = require('lodash')
const Promise = require('bluebird') const getos = require('getos')
const getos = Promise.promisify(require('getos'))
const os = require('os') const os = require('os')
const filesize = require('filesize') const filesize = require('filesize')
const path = require('path') const path = require('path')
@ -11,6 +10,8 @@ const request = require('request-promise')
const crypto = require('crypto') const crypto = require('crypto')
const nanoid = require('nanoid/non-secure').customAlphabet('1234567890abcdef', 10) const nanoid = require('nanoid/non-secure').customAlphabet('1234567890abcdef', 10)
const getosAsync = require('util').promisify(getos)
/* global WIKI */ /* global WIKI */
const dbTypes = { const dbTypes = {
@ -371,7 +372,7 @@ module.exports = {
async operatingSystem () { async operatingSystem () {
let osLabel = `${os.type()} (${os.platform()}) ${os.release()} ${os.arch()}` let osLabel = `${os.type()} (${os.platform()}) ${os.release()} ${os.arch()}`
if (os.platform() === 'linux') { if (os.platform() === 'linux') {
const osInfo = await getos() const osInfo = await getosAsync()
osLabel = `${os.type()} - ${osInfo.dist} (${osInfo.codename || os.platform()}) ${osInfo.release || os.release()} ${os.arch()}` osLabel = `${os.type()} - ${osInfo.dist} (${osInfo.codename || os.platform()}) ${osInfo.release || os.release()} ${os.arch()}`
} }
return osLabel return osLabel

@ -62,8 +62,12 @@ module.exports = {
} }
}, },
UserMutation: { UserMutation: {
async create (obj, args) { async create (obj, args, context) {
try { try {
if (!(await WIKI.auth.checkAssignUserToGroupAccess(context.req.user, args.groups))) {
throw new Error('You are not authorized to create a user with an assignment to an administrative group.')
}
await WIKI.models.users.createNewUser(args) await WIKI.models.users.createNewUser(args)
return { return {
@ -94,12 +98,16 @@ module.exports = {
} }
} }
}, },
async update (obj, args) { async update (obj, args, context) {
try { try {
if (!(await WIKI.auth.checkAssignUserToGroupAccess(context.req.user, args.groups))) {
throw new Error('You are not authorized to modify / assign a user from / to an administrative group.')
}
await WIKI.models.users.updateUser(args) await WIKI.models.users.updateUser(args)
return { return {
responseResult: graphHelper.generateSuccess('User created successfully') responseResult: graphHelper.generateSuccess('User updated successfully')
} }
} catch (err) { } catch (err) {
return graphHelper.generateError(err) return graphHelper.generateError(err)

@ -18,7 +18,7 @@ type GroupQuery {
list( list(
filter: String filter: String
orderBy: String orderBy: String
): [GroupMinimal] @auth(requires: ["write:groups", "manage:groups", "manage:system"]) ): [GroupMinimal] @auth(requires: ["write:users", "manage:users", "write:groups", "manage:groups", "manage:system"])
single( single(
id: Int! id: Int!
@ -49,12 +49,12 @@ type GroupMutation {
assignUser( assignUser(
groupId: Int! groupId: Int!
userId: Int! userId: Int!
): DefaultResponse @auth(requires: ["write:groups", "manage:groups", "manage:system"]) ): DefaultResponse @auth(requires: ["manage:users", "write:groups", "manage:groups", "manage:system"])
unassignUser( unassignUser(
groupId: Int! groupId: Int!
userId: Int! userId: Int!
): DefaultResponse @auth(requires: ["write:groups", "manage:groups", "manage:system"]) ): DefaultResponse @auth(requires: ["manage:users", "write:groups", "manage:groups", "manage:system"])
} }
# ----------------------------------------------- # -----------------------------------------------

@ -1,4 +1,7 @@
/* global WIKI */
const _ = require('lodash') const _ = require('lodash')
const { DateTime } = require('luxon')
module.exports = { module.exports = {
/** /**
@ -38,5 +41,11 @@ module.exports = {
}) })
return result return result
}, {}) }, {})
},
getCookieOpts () {
return {
expires: DateTime.utc().plus({ days: 365 }).toJSDate(),
...(WIKI.config.host.startsWith('https://') ? { secure: true } : {})
}
} }
} }

@ -48,6 +48,19 @@ module.exports = {
picture: '' picture: ''
} }
}) })
if (conf.mapGroups) {
const groups = _.get(profile, '_json.groups')
if (groups && _.isArray(groups)) {
const currentGroups = (await user.$relatedQuery('groups').select('groups.id')).map(g => g.id)
const expectedGroups = Object.values(WIKI.auth.groups).filter(g => groups.includes(g.name)).map(g => g.id)
for (const groupId of _.difference(expectedGroups, currentGroups)) {
await user.$relatedQuery('groups').relate(groupId)
}
for (const groupId of _.difference(currentGroups, expectedGroups)) {
await user.$relatedQuery('groups').unrelate().where('groupId', groupId)
}
}
}
cb(null, user) cb(null, user)
} catch (err) { } catch (err) {
cb(err, null) cb(err, null)

@ -27,3 +27,9 @@ props:
title: Cookie Encryption Key String title: Cookie Encryption Key String
hint: Random string with 44-character length. Setting this enables workaround for Chrome's SameSite cookies. hint: Random string with 44-character length. Setting this enables workaround for Chrome's SameSite cookies.
order: 3 order: 3
mapGroups:
type: Boolean
title: Map Groups
hint: Map groups matching names from the groups claim value
default: false
order: 4

@ -27,6 +27,14 @@ module.exports = {
passport.use(conf.key, passport.use(conf.key,
new GitHubStrategy(githubConfig, async (req, accessToken, refreshToken, profile, cb) => { new GitHubStrategy(githubConfig, async (req, accessToken, refreshToken, profile, cb) => {
try { try {
WIKI.logger.info(`GitHub OAuth: Processing profile for user ${profile.id || profile.username}`)
// Ensure email is available - passport-github2 should fetch it automatically with user:email scope
// but we'll log a warning if it's missing
if (!profile.emails || (Array.isArray(profile.emails) && profile.emails.length === 0)) {
WIKI.logger.warn(`GitHub OAuth: No email found in profile for user ${profile.id || profile.username}. Make sure 'user:email' scope is granted.`)
}
const user = await WIKI.models.users.processProfile({ const user = await WIKI.models.users.processProfile({
providerKey: req.params.strategy, providerKey: req.params.strategy,
profile: { profile: {
@ -34,9 +42,19 @@ module.exports = {
picture: _.get(profile, 'photos[0].value', '') picture: _.get(profile, 'photos[0].value', '')
} }
}) })
WIKI.logger.info(`GitHub OAuth: Successfully authenticated user ${user.email}`)
cb(null, user) cb(null, user)
} catch (err) { } catch (err) {
cb(err, null) WIKI.logger.warn(`GitHub OAuth: Authentication failed for strategy ${req.params.strategy}:`, err)
// Provide more user-friendly error messages
if (err.message && err.message.includes('email')) {
cb(new Error('GitHub authentication failed: Email address is required but not available. Please ensure your GitHub account has a verified email address and grant email access permissions.'), null)
} else if (err instanceof WIKI.Error.AuthAccountBanned) {
cb(err, null)
} else {
cb(new Error(`GitHub authentication failed: ${err.message || 'Unknown error'}`), null)
}
} }
} }
)) ))

@ -16,9 +16,13 @@ module.exports = {
passReqToCallback: true passReqToCallback: true
}, async (req, accessToken, refreshToken, profile, cb) => { }, async (req, accessToken, refreshToken, profile, cb) => {
try { try {
if (conf.hostedDomain && conf.hostedDomain != profile._json.hd) { WIKI.logger.info(`Google OAuth: Processing profile for user ${profile.id || profile.displayName}`)
throw new Error('Google authentication should have been performed with domain ' + conf.hostedDomain)
// Validate hosted domain if configured
if (conf.hostedDomain && profile._json.hd !== conf.hostedDomain) {
throw new Error(`Google authentication failed: User must be from domain ${conf.hostedDomain}, but got ${profile._json.hd || 'unknown'}`)
} }
const user = await WIKI.models.users.processProfile({ const user = await WIKI.models.users.processProfile({
providerKey: req.params.strategy, providerKey: req.params.strategy,
profile: { profile: {
@ -26,9 +30,21 @@ module.exports = {
picture: _.get(profile, 'photos[0].value', '') picture: _.get(profile, 'photos[0].value', '')
} }
}) })
WIKI.logger.info(`Google OAuth: Successfully authenticated user ${user.email}`)
cb(null, user) cb(null, user)
} catch (err) { } catch (err) {
cb(err, null) WIKI.logger.warn(`Google OAuth: Authentication failed for strategy ${req.params.strategy}:`, err)
// Provide more user-friendly error messages
if (err.message && err.message.includes('domain')) {
cb(new Error(`Google authentication failed: ${err.message}`), null)
} else if (err.message && err.message.includes('email')) {
cb(new Error('Google authentication failed: Email address is required but not available. Please ensure your Google account has a verified email address.'), null)
} else if (err instanceof WIKI.Error.AuthAccountBanned) {
cb(err, null)
} else {
cb(new Error(`Google authentication failed: ${err.message || 'Unknown error'}`), null)
}
} }
}) })

@ -35,17 +35,17 @@ props:
authorizationURL: authorizationURL:
type: String type: String
title: Authorization Endpoint URL title: Authorization Endpoint URL
hint: e.g. https://KEYCLOAK-HOST/auth/realms/YOUR-REALM/protocol/openid-connect/auth hint: e.g. https://KEYCLOAK-HOST/realms/YOUR-REALM/protocol/openid-connect/auth
order: 5 order: 5
tokenURL: tokenURL:
type: String type: String
title: Token Endpoint URL title: Token Endpoint URL
hint: e.g. https://KEYCLOAK-HOST/auth/realms/YOUR-REALM/protocol/openid-connect/token hint: e.g. https://KEYCLOAK-HOST/realms/YOUR-REALM/protocol/openid-connect/token
order: 6 order: 6
userInfoURL: userInfoURL:
type: String type: String
title: User Info Endpoint URL title: User Info Endpoint URL
hint: e.g. https://KEYCLOAK-HOST/auth/realms/YOUR-REALM/protocol/openid-connect/userinfo hint: e.g. https://KEYCLOAK-HOST/realms/YOUR-REALM/protocol/openid-connect/userinfo
order: 7 order: 7
logoutUpstream: logoutUpstream:
type: Boolean type: Boolean
@ -55,7 +55,7 @@ props:
logoutURL: logoutURL:
type: String type: String
title: Logout Endpoint URL title: Logout Endpoint URL
hint: e.g. https://KEYCLOAK-HOST/auth/realms/YOUR-REALM/protocol/openid-connect/logout hint: e.g. https://KEYCLOAK-HOST/realms/YOUR-REALM/protocol/openid-connect/logout
order: 9 order: 9
logoutUpstreamRedirectLegacy: logoutUpstreamRedirectLegacy:
type: Boolean type: Boolean

@ -22,13 +22,15 @@ module.exports = {
state: conf.enableCSRFProtection state: conf.enableCSRFProtection
}, async (req, accessToken, refreshToken, profile, cb) => { }, async (req, accessToken, refreshToken, profile, cb) => {
try { try {
const picture = _.get(profile, conf.pictureClaim, '')
const user = await WIKI.models.users.processProfile({ const user = await WIKI.models.users.processProfile({
providerKey: req.params.strategy, providerKey: req.params.strategy,
profile: { profile: {
...profile, ...profile,
id: _.get(profile, conf.userIdClaim), id: _.get(profile, conf.userIdClaim),
displayName: _.get(profile, conf.displayNameClaim, '???'), displayName: _.get(profile, conf.displayNameClaim, '???'),
email: _.get(profile, conf.emailClaim) email: _.get(profile, conf.emailClaim),
picture: picture
} }
}) })
if (conf.mapGroups) { if (conf.mapGroups) {

@ -54,38 +54,45 @@ props:
default: email default: email
maxWidth: 500 maxWidth: 500
order: 8 order: 8
pictureClaim:
type: String
title: Picture Claim
hint: Field containing the user avatar URL
default: picture
maxWidth: 500
order: 9
mapGroups: mapGroups:
type: Boolean type: Boolean
title: Map Groups title: Map Groups
hint: Map groups matching names from the groups claim value hint: Map groups matching names from the groups claim value
default: false default: false
order: 9 order: 10
groupsClaim: groupsClaim:
type: String type: String
title: Groups Claim title: Groups Claim
hint: Field containing the group names hint: Field containing the group names
default: groups default: groups
maxWidth: 500 maxWidth: 500
order: 10 order: 11
logoutURL: logoutURL:
type: String type: String
title: Logout URL title: Logout URL
hint: (optional) Logout URL on the OAuth2 provider where the user will be redirected to complete the logout process. hint: (optional) Logout URL on the OAuth2 provider where the user will be redirected to complete the logout process.
order: 11 order: 12
scope: scope:
type: String type: String
title: Scope title: Scope
hint: (optional) Application Client permission scopes. hint: (optional) Application Client permission scopes.
order: 12 order: 13
useQueryStringForAccessToken: useQueryStringForAccessToken:
type: Boolean type: Boolean
default: false default: false
title: Pass access token via GET query string to User Info Endpoint title: Pass access token via GET query string to User Info Endpoint
hint: (optional) Pass the access token in an `access_token` parameter attached to the GET query string of the User Info Endpoint URL. Otherwise the access token will be passed in the Authorization header. hint: (optional) Pass the access token in an `access_token` parameter attached to the GET query string of the User Info Endpoint URL. Otherwise the access token will be passed in the Authorization header.
order: 13 order: 14
enableCSRFProtection: enableCSRFProtection:
type: Boolean type: Boolean
default: true default: true
title: Enable CSRF protection title: Enable CSRF protection
hint: Pass a nonce state parameter during authentication to protect against CSRF attacks. hint: Pass a nonce state parameter during authentication to protect against CSRF attacks.
order: 14 order: 15

@ -24,6 +24,7 @@ module.exports = {
acrValues: conf.acrValues acrValues: conf.acrValues
}, async (req, iss, uiProfile, idProfile, context, idToken, accessToken, refreshToken, params, cb) => { }, async (req, iss, uiProfile, idProfile, context, idToken, accessToken, refreshToken, params, cb) => {
const profile = Object.assign({}, idProfile, uiProfile) const profile = Object.assign({}, idProfile, uiProfile)
const picture = _.get(profile, '_json.' + conf.pictureClaim, '')
try { try {
const user = await WIKI.models.users.processProfile({ const user = await WIKI.models.users.processProfile({
@ -31,7 +32,8 @@ module.exports = {
profile: { profile: {
...profile, ...profile,
email: _.get(profile, '_json.' + conf.emailClaim), email: _.get(profile, '_json.' + conf.emailClaim),
displayName: _.get(profile, '_json.' + conf.displayNameClaim, '') displayName: _.get(profile, '_json.' + conf.displayNameClaim, ''),
picture: picture
} }
}) })
if (conf.mapGroups) { if (conf.mapGroups) {

@ -62,26 +62,33 @@ props:
default: displayName default: displayName
maxWidth: 500 maxWidth: 500
order: 9 order: 9
pictureClaim:
type: String
title: Picture Claim
hint: Field containing the user avatar URL
default: picture
maxWidth: 500
order: 10
mapGroups: mapGroups:
type: Boolean type: Boolean
title: Map Groups title: Map Groups
hint: Map groups matching names from the groups claim value hint: Map groups matching names from the groups claim value
default: false default: false
order: 10 order: 11
groupsClaim: groupsClaim:
type: String type: String
title: Groups Claim title: Groups Claim
hint: Field containing the group names hint: Field containing the group names
default: groups default: groups
maxWidth: 500 maxWidth: 500
order: 11 order: 12
logoutURL: logoutURL:
type: String type: String
title: Logout URL title: Logout URL
hint: (optional) Logout URL on the OAuth2 provider where the user will be redirected to complete the logout process. hint: (optional) Logout URL on the OAuth2 provider where the user will be redirected to complete the logout process.
order: 12 order: 13
acrValues: acrValues:
type: String type: String
title: ACR Values title: ACR Values
hint: (optional) Authentication Context Class Reference hint: (optional) Authentication Context Class Reference
order: 13 order: 14

@ -12,7 +12,26 @@ module.exports = {
init (passport, conf) { init (passport, conf) {
const siteURL = conf.siteURL.slice(-1) === '/' ? conf.siteURL.slice(0, -1) : conf.siteURL const siteURL = conf.siteURL.slice(-1) === '/' ? conf.siteURL.slice(0, -1) : conf.siteURL
OAuth2Strategy.prototype.userProfile = function (accessToken, cb) { const strategyInstance = new OAuth2Strategy({
authorizationURL: `${siteURL}/oauth/authorize`,
tokenURL: `${siteURL}/oauth/token`,
clientID: conf.clientId,
clientSecret: conf.clientSecret,
callbackURL: conf.callbackURL,
passReqToCallback: true
}, async (req, accessToken, refreshToken, profile, cb) => {
try {
const user = await WIKI.models.users.processProfile({
providerKey: req.params.strategy,
profile
})
cb(null, user)
} catch (err) {
cb(err, null)
}
})
strategyInstance.userProfile = function (accessToken, cb) {
this._oauth2.get(`${siteURL}/api/v1/me`, accessToken, (err, body, res) => { this._oauth2.get(`${siteURL}/api/v1/me`, accessToken, (err, body, res) => {
if (err) { if (err) {
WIKI.logger.warn('Rocket.chat - Failed to fetch user profile.') WIKI.logger.warn('Rocket.chat - Failed to fetch user profile.')
@ -33,26 +52,7 @@ module.exports = {
}) })
} }
passport.use(conf.key, passport.use(conf.key, strategyInstance)
new OAuth2Strategy({
authorizationURL: `${siteURL}/oauth/authorize`,
tokenURL: `${siteURL}/oauth/token`,
clientID: conf.clientId,
clientSecret: conf.clientSecret,
callbackURL: conf.callbackURL,
passReqToCallback: true
}, async (req, accessToken, refreshToken, profile, cb) => {
try {
const user = await WIKI.models.users.processProfile({
providerKey: req.params.strategy,
profile
})
cb(null, user)
} catch (err) {
cb(err, null)
}
})
)
}, },
logout (conf) { logout (conf) {
if (!conf.logoutURL) { if (!conf.logoutURL) {

@ -34,7 +34,8 @@ module.exports = {
input = DOMPurify.sanitize(input, { input = DOMPurify.sanitize(input, {
ADD_ATTR: allowedAttrs, ADD_ATTR: allowedAttrs,
ADD_TAGS: allowedTags ADD_TAGS: allowedTags,
HTML_INTEGRATION_POINTS: { foreignobject: true }
}) })
} }
return input return input

@ -1,8 +1,7 @@
const _ = require('lodash') const _ = require('lodash')
const algoliasearch = require('algoliasearch') const algoliasearch = require('algoliasearch')
const stream = require('stream') const { pipeline } = require('node:stream/promises')
const Promise = require('bluebird') const { Transform } = require('node:stream')
const pipeline = Promise.promisify(stream.pipeline)
/* global WIKI */ /* global WIKI */
@ -192,7 +191,7 @@ module.exports = {
isPublished: true, isPublished: true,
isPrivate: false isPrivate: false
}).stream(), }).stream(),
new stream.Transform({ new Transform({
objectMode: true, objectMode: true,
transform: async (chunk, enc, cb) => processDocument(cb, chunk), transform: async (chunk, enc, cb) => processDocument(cb, chunk),
flush: async (cb) => processDocument(cb) flush: async (cb) => processDocument(cb)

@ -1,8 +1,7 @@
const _ = require('lodash') const _ = require('lodash')
const AWS = require('aws-sdk') const AWS = require('aws-sdk')
const stream = require('stream') const { pipeline } = require('node:stream/promises')
const Promise = require('bluebird') const { Transform } = require('node:stream')
const pipeline = Promise.promisify(stream.pipeline)
/* global WIKI */ /* global WIKI */
@ -353,7 +352,7 @@ module.exports = {
isPublished: true, isPublished: true,
isPrivate: false isPrivate: false
}).stream(), }).stream(),
new stream.Transform({ new Transform({
objectMode: true, objectMode: true,
transform: async (chunk, enc, cb) => processDocument(cb, chunk), transform: async (chunk, enc, cb) => processDocument(cb, chunk),
flush: async (cb) => processDocument(cb) flush: async (cb) => processDocument(cb)

@ -1,9 +1,8 @@
const _ = require('lodash') const _ = require('lodash')
const { SearchService, QueryType } = require('azure-search-client') const { SearchService, QueryType } = require('azure-search-client')
const request = require('request-promise') const request = require('request-promise')
const stream = require('stream') const { pipeline } = require('node:stream/promises')
const Promise = require('bluebird') const { Transform } = require('node:stream')
const pipeline = Promise.promisify(stream.pipeline)
/* global WIKI */ /* global WIKI */
@ -215,7 +214,7 @@ module.exports = {
isPublished: true, isPublished: true,
isPrivate: false isPrivate: false
}).stream(), }).stream(),
new stream.Transform({ new Transform({
objectMode: true, objectMode: true,
transform: (chunk, enc, cb) => { transform: (chunk, enc, cb) => {
cb(null, { cb(null, {

@ -1,8 +1,7 @@
const _ = require('lodash') const _ = require('lodash')
const stream = require('stream')
const Promise = require('bluebird')
const fs = require('fs') const fs = require('fs')
const pipeline = Promise.promisify(stream.pipeline) const { pipeline } = require('node:stream/promises')
const { Transform } = require('node:stream')
/* global WIKI */ /* global WIKI */
@ -392,7 +391,7 @@ module.exports = {
isPublished: true, isPublished: true,
isPrivate: false isPrivate: false
}).stream(), }).stream(),
new stream.Transform({ new Transform({
objectMode: true, objectMode: true,
transform: async (chunk, enc, cb) => processDocument(cb, chunk), transform: async (chunk, enc, cb) => processDocument(cb, chunk),
flush: async (cb) => processDocument(cb) flush: async (cb) => processDocument(cb)

@ -1,7 +1,6 @@
const tsquery = require('pg-tsquery')() const tsquery = require('pg-tsquery')()
const stream = require('stream') const { pipeline } = require('node:stream/promises')
const Promise = require('bluebird') const { Transform } = require('node:stream')
const pipeline = Promise.promisify(stream.pipeline)
/* global WIKI */ /* global WIKI */
@ -23,6 +22,9 @@ module.exports = {
async init() { async init() {
WIKI.logger.info(`(SEARCH/POSTGRES) Initializing...`) WIKI.logger.info(`(SEARCH/POSTGRES) Initializing...`)
// -> Ensure pg_trgm extension is available (required for similarity search)
await WIKI.models.knex.raw('CREATE EXTENSION IF NOT EXISTS pg_trgm')
// -> Create Search Index // -> Create Search Index
const indexExists = await WIKI.models.knex.schema.hasTable('pagesVector') const indexExists = await WIKI.models.knex.schema.hasTable('pagesVector')
if (!indexExists) { if (!indexExists) {
@ -45,7 +47,6 @@ module.exports = {
CREATE TABLE "pagesWords" AS SELECT word FROM ts_stat( CREATE TABLE "pagesWords" AS SELECT word FROM ts_stat(
'SELECT to_tsvector(''simple'', "title") || to_tsvector(''simple'', "description") || to_tsvector(''simple'', "content") FROM "pagesVector"' 'SELECT to_tsvector(''simple'', "title") || to_tsvector(''simple'', "description") || to_tsvector(''simple'', "content") FROM "pagesVector"'
)`) )`)
await WIKI.models.knex.raw('CREATE EXTENSION IF NOT EXISTS pg_trgm')
await WIKI.models.knex.raw(`CREATE INDEX "pageWords_idx" ON "pagesWords" USING GIN (word gin_trgm_ops)`) await WIKI.models.knex.raw(`CREATE INDEX "pageWords_idx" ON "pagesWords" USING GIN (word gin_trgm_ops)`)
} }
@ -81,8 +82,12 @@ module.exports = {
${qryEnd} ${qryEnd}
`, qryParams) `, qryParams)
if (results.rows.length < 5) { if (results.rows.length < 5) {
const suggestResults = await WIKI.models.knex.raw(`SELECT word, word <-> ? AS rank FROM "pagesWords" WHERE similarity(word, ?) > 0.2 ORDER BY rank LIMIT 5;`, [q, q]) try {
suggestions = suggestResults.rows.map(r => r.word) const suggestResults = await WIKI.models.knex.raw(`SELECT word, word <-> ? AS rank FROM "pagesWords" WHERE similarity(word, ?) > 0.2 ORDER BY rank LIMIT 5;`, [q, q])
suggestions = suggestResults.rows.map(r => r.word)
} catch (err) {
WIKI.logger.warn(`Search Engine Suggestion Error (pg_trgm extension may be missing): ${err.message}`)
}
} }
return { return {
results: results.rows, results: results.rows,
@ -160,7 +165,7 @@ module.exports = {
isPublished: true, isPublished: true,
isPrivate: false isPrivate: false
}).stream(), }).stream(),
new stream.Transform({ new Transform({
objectMode: true, objectMode: true,
transform: async (page, enc, cb) => { transform: async (page, enc, cb) => {
const content = WIKI.models.pages.cleanHTML(page.render) const content = WIKI.models.pages.cleanHTML(page.render)

@ -1,7 +1,6 @@
const { BlobServiceClient, StorageSharedKeyCredential } = require('@azure/storage-blob') const { BlobServiceClient, StorageSharedKeyCredential } = require('@azure/storage-blob')
const stream = require('stream') const { pipeline } = require('node:stream/promises')
const Promise = require('bluebird') const { Transform } = require('node:stream')
const pipeline = Promise.promisify(stream.pipeline)
const pageHelper = require('../../../helpers/page.js') const pageHelper = require('../../../helpers/page.js')
const _ = require('lodash') const _ = require('lodash')
@ -129,7 +128,7 @@ module.exports = {
WIKI.models.knex.column('path', 'localeCode', 'title', 'description', 'contentType', 'content', 'isPublished', 'updatedAt', 'createdAt').select().from('pages').where({ WIKI.models.knex.column('path', 'localeCode', 'title', 'description', 'contentType', 'content', 'isPublished', 'updatedAt', 'createdAt').select().from('pages').where({
isPrivate: false isPrivate: false
}).stream(), }).stream(),
new stream.Transform({ new Transform({
objectMode: true, objectMode: true,
transform: async (page, enc, cb) => { transform: async (page, enc, cb) => {
const filePath = getFilePath(page, 'path') const filePath = getFilePath(page, 'path')
@ -147,7 +146,7 @@ module.exports = {
await pipeline( await pipeline(
WIKI.models.knex.column('filename', 'folderId', 'data').select().from('assets').join('assetData', 'assets.id', '=', 'assetData.id').stream(), WIKI.models.knex.column('filename', 'folderId', 'data').select().from('assets').join('assetData', 'assets.id', '=', 'assetData.id').stream(),
new stream.Transform({ new Transform({
objectMode: true, objectMode: true,
transform: async (asset, enc, cb) => { transform: async (asset, enc, cb) => {
const filename = (asset.folderId && asset.folderId > 0) ? `${_.get(assetFolders, asset.folderId)}/${asset.filename}` : asset.filename const filename = (asset.folderId && asset.folderId > 0) ? `${_.get(assetFolders, asset.folderId)}/${asset.filename}` : asset.filename

@ -22,6 +22,7 @@ props:
- sfo2.digitaloceanspaces.com - sfo2.digitaloceanspaces.com
- sfo3.digitaloceanspaces.com - sfo3.digitaloceanspaces.com
- sgp1.digitaloceanspaces.com - sgp1.digitaloceanspaces.com
- tor1.digitaloceanspaces.com
order: 1 order: 1
bucket: bucket:
type: String type: String

@ -1,8 +1,7 @@
const fs = require('fs-extra') const fs = require('fs-extra')
const path = require('path') const path = require('path')
const stream = require('stream') const { pipeline } = require('stream/promises')
const Promise = require('bluebird') const { Transform } = require('stream')
const pipeline = Promise.promisify(stream.pipeline)
const klaw = require('klaw') const klaw = require('klaw')
const mime = require('mime-types').lookup const mime = require('mime-types').lookup
const _ = require('lodash') const _ = require('lodash')
@ -22,7 +21,7 @@ module.exports = {
return !_.includes(f, '.git') return !_.includes(f, '.git')
} }
}), }),
new stream.Transform({ new Transform({
objectMode: true, objectMode: true,
transform: async (file, enc, cb) => { transform: async (file, enc, cb) => {
const relPath = file.path.substr(fullPath.length + 1) const relPath = file.path.substr(fullPath.length + 1)

@ -2,10 +2,9 @@ const fs = require('fs-extra')
const path = require('path') const path = require('path')
const tar = require('tar-fs') const tar = require('tar-fs')
const zlib = require('zlib') const zlib = require('zlib')
const stream = require('stream')
const _ = require('lodash') const _ = require('lodash')
const Promise = require('bluebird') const { pipeline } = require('node:stream/promises')
const pipeline = Promise.promisify(stream.pipeline) const { Transform } = require('node:stream')
const moment = require('moment') const moment = require('moment')
const pageHelper = require('../../../helpers/page') const pageHelper = require('../../../helpers/page')
@ -130,7 +129,7 @@ module.exports = {
WIKI.models.knex.column('id', 'path', 'localeCode', 'title', 'description', 'contentType', 'content', 'isPublished', 'updatedAt', 'createdAt', 'editorKey').select().from('pages').where({ WIKI.models.knex.column('id', 'path', 'localeCode', 'title', 'description', 'contentType', 'content', 'isPublished', 'updatedAt', 'createdAt', 'editorKey').select().from('pages').where({
isPrivate: false isPrivate: false
}).stream(), }).stream(),
new stream.Transform({ new Transform({
objectMode: true, objectMode: true,
transform: async (page, enc, cb) => { transform: async (page, enc, cb) => {
const pageObject = await WIKI.models.pages.query().findById(page.id) const pageObject = await WIKI.models.pages.query().findById(page.id)
@ -153,7 +152,7 @@ module.exports = {
await pipeline( await pipeline(
WIKI.models.knex.column('filename', 'folderId', 'data').select().from('assets').join('assetData', 'assets.id', '=', 'assetData.id').stream(), WIKI.models.knex.column('filename', 'folderId', 'data').select().from('assets').join('assetData', 'assets.id', '=', 'assetData.id').stream(),
new stream.Transform({ new Transform({
objectMode: true, objectMode: true,
transform: async (asset, enc, cb) => { transform: async (asset, enc, cb) => {
const filename = (asset.folderId && asset.folderId > 0) ? `${_.get(assetFolders, asset.folderId)}/${asset.filename}` : asset.filename const filename = (asset.folderId && asset.folderId > 0) ? `${_.get(assetFolders, asset.folderId)}/${asset.filename}` : asset.filename

@ -2,9 +2,8 @@ const path = require('path')
const sgit = require('simple-git') const sgit = require('simple-git')
const fs = require('fs-extra') const fs = require('fs-extra')
const _ = require('lodash') const _ = require('lodash')
const stream = require('stream') const { pipeline } = require('node:stream/promises')
const Promise = require('bluebird') const { Transform } = require('node:stream')
const pipeline = Promise.promisify(stream.pipeline)
const klaw = require('klaw') const klaw = require('klaw')
const os = require('os') const os = require('os')
@ -441,7 +440,7 @@ module.exports = {
return !_.includes(f, '.git') return !_.includes(f, '.git')
} }
}), }),
new stream.Transform({ new Transform({
objectMode: true, objectMode: true,
transform: async (file, enc, cb) => { transform: async (file, enc, cb) => {
const relPath = file.path.substr(this.repoPath.length + 1) const relPath = file.path.substr(this.repoPath.length + 1)
@ -476,7 +475,7 @@ module.exports = {
WIKI.models.knex.column('id', 'path', 'localeCode', 'title', 'description', 'contentType', 'content', 'isPublished', 'updatedAt', 'createdAt', 'editorKey').select().from('pages').where({ WIKI.models.knex.column('id', 'path', 'localeCode', 'title', 'description', 'contentType', 'content', 'isPublished', 'updatedAt', 'createdAt', 'editorKey').select().from('pages').where({
isPrivate: false isPrivate: false
}).stream(), }).stream(),
new stream.Transform({ new Transform({
objectMode: true, objectMode: true,
transform: async (page, enc, cb) => { transform: async (page, enc, cb) => {
const pageObject = await WIKI.models.pages.query().findById(page.id) const pageObject = await WIKI.models.pages.query().findById(page.id)
@ -500,7 +499,7 @@ module.exports = {
await pipeline( await pipeline(
WIKI.models.knex.column('filename', 'folderId', 'data').select().from('assets').join('assetData', 'assets.id', '=', 'assetData.id').stream(), WIKI.models.knex.column('filename', 'folderId', 'data').select().from('assets').join('assetData', 'assets.id', '=', 'assetData.id').stream(),
new stream.Transform({ new Transform({
objectMode: true, objectMode: true,
transform: async (asset, enc, cb) => { transform: async (asset, enc, cb) => {
const filename = (asset.folderId && asset.folderId > 0) ? `${_.get(assetFolders, asset.folderId)}/${asset.filename}` : asset.filename const filename = (asset.folderId && asset.folderId > 0) ? `${_.get(assetFolders, asset.folderId)}/${asset.filename}` : asset.filename

@ -1,7 +1,6 @@
const S3 = require('aws-sdk/clients/s3') const S3 = require('aws-sdk/clients/s3')
const stream = require('stream') const { pipeline } = require('node:stream/promises')
const Promise = require('bluebird') const { Transform } = require('node:stream')
const pipeline = Promise.promisify(stream.pipeline)
const _ = require('lodash') const _ = require('lodash')
const pageHelper = require('../../../helpers/page.js') const pageHelper = require('../../../helpers/page.js')
@ -22,7 +21,7 @@ const getFilePath = (page, pathKey) => {
module.exports = class S3CompatibleStorage { module.exports = class S3CompatibleStorage {
constructor(storageName) { constructor(storageName) {
this.storageName = storageName this.storageName = storageName
this.bucketName = "" this.bucketName = ''
} }
async activated() { async activated() {
// not used // not used
@ -136,7 +135,7 @@ module.exports = class S3CompatibleStorage {
WIKI.models.knex.column('path', 'localeCode', 'title', 'description', 'contentType', 'content', 'isPublished', 'updatedAt', 'createdAt').select().from('pages').where({ WIKI.models.knex.column('path', 'localeCode', 'title', 'description', 'contentType', 'content', 'isPublished', 'updatedAt', 'createdAt').select().from('pages').where({
isPrivate: false isPrivate: false
}).stream(), }).stream(),
new stream.Transform({ new Transform({
objectMode: true, objectMode: true,
transform: async (page, enc, cb) => { transform: async (page, enc, cb) => {
const filePath = getFilePath(page, 'path') const filePath = getFilePath(page, 'path')
@ -152,7 +151,7 @@ module.exports = class S3CompatibleStorage {
await pipeline( await pipeline(
WIKI.models.knex.column('filename', 'folderId', 'data').select().from('assets').join('assetData', 'assets.id', '=', 'assetData.id').stream(), WIKI.models.knex.column('filename', 'folderId', 'data').select().from('assets').join('assetData', 'assets.id', '=', 'assetData.id').stream(),
new stream.Transform({ new Transform({
objectMode: true, objectMode: true,
transform: async (asset, enc, cb) => { transform: async (asset, enc, cb) => {
const filename = (asset.folderId && asset.folderId > 0) ? `${_.get(assetFolders, asset.folderId)}/${asset.filename}` : asset.filename const filename = (asset.folderId && asset.folderId > 0) ? `${_.get(assetFolders, asset.folderId)}/${asset.filename}` : asset.filename

@ -1,9 +1,8 @@
const SSH2Promise = require('ssh2-promise') const SSH2Promise = require('ssh2-promise')
const _ = require('lodash') const _ = require('lodash')
const path = require('path') const path = require('path')
const stream = require('stream') const { pipeline } = require('node:stream/promises')
const Promise = require('bluebird') const { Transform } = require('node:stream')
const pipeline = Promise.promisify(stream.pipeline)
const pageHelper = require('../../../helpers/page.js') const pageHelper = require('../../../helpers/page.js')
/* global WIKI */ /* global WIKI */
@ -118,7 +117,7 @@ module.exports = {
WIKI.models.knex.column('path', 'localeCode', 'title', 'description', 'contentType', 'content', 'isPublished', 'updatedAt', 'createdAt').select().from('pages').where({ WIKI.models.knex.column('path', 'localeCode', 'title', 'description', 'contentType', 'content', 'isPublished', 'updatedAt', 'createdAt').select().from('pages').where({
isPrivate: false isPrivate: false
}).stream(), }).stream(),
new stream.Transform({ new Transform({
objectMode: true, objectMode: true,
transform: async (page, enc, cb) => { transform: async (page, enc, cb) => {
const filePath = getFilePath(page, 'path') const filePath = getFilePath(page, 'path')
@ -135,7 +134,7 @@ module.exports = {
await pipeline( await pipeline(
WIKI.models.knex.column('filename', 'folderId', 'data').select().from('assets').join('assetData', 'assets.id', '=', 'assetData.id').stream(), WIKI.models.knex.column('filename', 'folderId', 'data').select().from('assets').join('assetData', 'assets.id', '=', 'assetData.id').stream(),
new stream.Transform({ new Transform({
objectMode: true, objectMode: true,
transform: async (asset, enc, cb) => { transform: async (asset, enc, cb) => {
const filename = (asset.folderId && asset.folderId > 0) ? `${_.get(assetFolders, asset.folderId)}/${asset.filename}` : asset.filename const filename = (asset.folderId && asset.folderId > 0) ? `${_.get(assetFolders, asset.folderId)}/${asset.filename}` : asset.filename

@ -5,13 +5,14 @@ const compression = require('compression')
const express = require('express') const express = require('express')
const favicon = require('serve-favicon') const favicon = require('serve-favicon')
const http = require('http') const http = require('http')
const Promise = require('bluebird')
const fs = require('fs-extra') const fs = require('fs-extra')
const _ = require('lodash') const _ = require('lodash')
const crypto = Promise.promisifyAll(require('crypto')) const crypto = require('crypto')
const pem2jwk = require('pem-jwk').pem2jwk const pem2jwk = require('pem-jwk').pem2jwk
const semver = require('semver') const semver = require('semver')
const randomBytesAsync = require('util').promisify(crypto.randomBytes)
/* global WIKI */ /* global WIKI */
module.exports = () => { module.exports = () => {
@ -119,7 +120,7 @@ module.exports = () => {
analyticsService: '', analyticsService: '',
analyticsId: '' analyticsId: ''
}) })
_.set(WIKI.config, 'sessionSecret', (await crypto.randomBytesAsync(32)).toString('hex')) _.set(WIKI.config, 'sessionSecret', (await randomBytesAsync(32)).toString('hex'))
_.set(WIKI.config, 'telemetry', { _.set(WIKI.config, 'telemetry', {
isEnabled: req.body.telemetry === true, isEnabled: req.body.telemetry === true,
clientId: uuid() clientId: uuid()

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