mirror of
https://github.com/NodeBB/NodeBB.git
synced 2025-11-14 01:45:47 +01:00
Merge remote-tracking branch 'origin/develop' into activitypub
This commit is contained in:
224
CHANGELOG.md
224
CHANGELOG.md
@@ -1,3 +1,227 @@
|
|||||||
|
#### v3.11.0 (2024-11-27)
|
||||||
|
|
||||||
|
##### Chores
|
||||||
|
|
||||||
|
* **i18n:**
|
||||||
|
* fallback strings for new resources: nodebb.admin-settings-reputation, nodebb.error (985e5e3d)
|
||||||
|
* fallback strings for new resources: nodebb.admin-manage-user-custom-fields, nodebb.error (174be398)
|
||||||
|
* fallback strings for new resources: nodebb.error (b676c220)
|
||||||
|
* fallback strings for new resources: nodebb.admin-manage-user-custom-fields (b89036cd)
|
||||||
|
* fallback strings for new resources: nodebb.admin-settings-uploads (e3ef858b)
|
||||||
|
* fallback strings for new resources: nodebb.admin-manage-users, nodebb.error (15e16704)
|
||||||
|
* fallback strings for new resources: nodebb.topic (9fd5ca27)
|
||||||
|
* fallback strings for new resources: nodebb.pages, nodebb.user (1c26048f)
|
||||||
|
* migrate admin/settings/social.json into admin/settings/general.json (206613dd)
|
||||||
|
* migrate admin/settings/languages.json into admin/settings/general.json (ab143b1d)
|
||||||
|
* migrate admin/settings/homepage.json into admin/settings/general.json (a7678185)
|
||||||
|
* combine admin/settings/guest.json language file into admin/settings/user.json (3f14373c)
|
||||||
|
* up themes (388a156d)
|
||||||
|
* up harmony (2324a5bf)
|
||||||
|
* up harmony (03cea237)
|
||||||
|
* up themes (2ec3d1af)
|
||||||
|
* up themes (965ca636)
|
||||||
|
* up persona (1a4a9d08)
|
||||||
|
* up peace (acd42c23)
|
||||||
|
* up peace (7179a280)
|
||||||
|
* up peace (bcf8dee2)
|
||||||
|
* up peace (524b2b76)
|
||||||
|
* up themes (4ad082b4)
|
||||||
|
* up peace (953ea609)
|
||||||
|
* up widgets (6a71fd24)
|
||||||
|
* up harmony (f13b7f8d)
|
||||||
|
* up persona (6f89bf6b)
|
||||||
|
* up persona (79c93c85)
|
||||||
|
* up themes (d63a828d)
|
||||||
|
* up themes (6aa8f1b6)
|
||||||
|
* up peace (a4d8baf3)
|
||||||
|
* up themes (24e9adf6)
|
||||||
|
* up persona (baff68b4)
|
||||||
|
* up peace (c4b50607)
|
||||||
|
* up lavender (c32c63aa)
|
||||||
|
* up lavender (66214bea)
|
||||||
|
* update tx config (4ce387b9)
|
||||||
|
* up themes (65f64eba)
|
||||||
|
* up themes (280e7613)
|
||||||
|
* up widgets (b13bd803)
|
||||||
|
* incrementing version number - v3.10.3 (57d54224)
|
||||||
|
* update changelog for v3.10.3 (83965385)
|
||||||
|
* up harmony (f6f1d906)
|
||||||
|
* incrementing version number - v3.10.2 (2f15f464)
|
||||||
|
* incrementing version number - v3.10.1 (cca3a644)
|
||||||
|
* up harmony (9426fd1a)
|
||||||
|
* incrementing version number - v3.10.0 (b60a9b4e)
|
||||||
|
* incrementing version number - v3.9.1 (f120c91c)
|
||||||
|
* incrementing version number - v3.9.0 (4880f32d)
|
||||||
|
* incrementing version number - v3.8.4 (4833f9a6)
|
||||||
|
* incrementing version number - v3.8.3 (97ce2c44)
|
||||||
|
* incrementing version number - v3.8.2 (72d91251)
|
||||||
|
* incrementing version number - v3.8.1 (527326f7)
|
||||||
|
* incrementing version number - v3.8.0 (e228a6eb)
|
||||||
|
* incrementing version number - v3.7.5 (6882894d)
|
||||||
|
* incrementing version number - v3.7.4 (6678744c)
|
||||||
|
* incrementing version number - v3.7.3 (2d62b6f6)
|
||||||
|
* incrementing version number - v3.7.2 (cc257e7e)
|
||||||
|
* incrementing version number - v3.7.1 (712365a5)
|
||||||
|
* incrementing version number - v3.7.0 (9a6153d7)
|
||||||
|
* incrementing version number - v3.6.7 (86a17e38)
|
||||||
|
* incrementing version number - v3.6.6 (6604bf37)
|
||||||
|
* incrementing version number - v3.6.5 (6c653625)
|
||||||
|
* incrementing version number - v3.6.4 (83d131b4)
|
||||||
|
* incrementing version number - v3.6.3 (fc7d2bfd)
|
||||||
|
* incrementing version number - v3.6.2 (0f577a57)
|
||||||
|
* incrementing version number - v3.6.1 (f1a69468)
|
||||||
|
* incrementing version number - v3.6.0 (4cdf85f8)
|
||||||
|
* incrementing version number - v3.5.3 (ed0e8783)
|
||||||
|
* incrementing version number - v3.5.2 (52fbb2da)
|
||||||
|
* incrementing version number - v3.5.1 (4c543488)
|
||||||
|
* incrementing version number - v3.5.0 (d06fb4f0)
|
||||||
|
* incrementing version number - v3.4.3 (5c984250)
|
||||||
|
* incrementing version number - v3.4.2 (3f0dac38)
|
||||||
|
* incrementing version number - v3.4.1 (01e69574)
|
||||||
|
* incrementing version number - v3.4.0 (fd9247c5)
|
||||||
|
* incrementing version number - v3.3.9 (5805e770)
|
||||||
|
* incrementing version number - v3.3.8 (a5603565)
|
||||||
|
* incrementing version number - v3.3.7 (b26f1744)
|
||||||
|
* incrementing version number - v3.3.6 (7fb38792)
|
||||||
|
* incrementing version number - v3.3.4 (a67f84ea)
|
||||||
|
* incrementing version number - v3.3.3 (f94d239b)
|
||||||
|
* incrementing version number - v3.3.2 (ec9dac97)
|
||||||
|
* incrementing version number - v3.3.1 (151cc68f)
|
||||||
|
* incrementing version number - v3.3.0 (fc1ad70f)
|
||||||
|
* incrementing version number - v3.2.3 (b06d3e63)
|
||||||
|
* incrementing version number - v3.2.2 (758ecfcd)
|
||||||
|
* incrementing version number - v3.2.1 (20145074)
|
||||||
|
* incrementing version number - v3.2.0 (9ecac38e)
|
||||||
|
* incrementing version number - v3.1.7 (0b4e81ab)
|
||||||
|
* incrementing version number - v3.1.6 (b3a3b130)
|
||||||
|
* incrementing version number - v3.1.5 (ec19343a)
|
||||||
|
* incrementing version number - v3.1.4 (2452783c)
|
||||||
|
* incrementing version number - v3.1.3 (3b4e9d3f)
|
||||||
|
* incrementing version number - v3.1.2 (40fa3489)
|
||||||
|
* incrementing version number - v3.1.1 (40250733)
|
||||||
|
* incrementing version number - v3.1.0 (0cb386bd)
|
||||||
|
* incrementing version number - v3.0.1 (26f6ea49)
|
||||||
|
* incrementing version number - v3.0.0 (224e08cd)
|
||||||
|
* **deps:**
|
||||||
|
* update commitlint monorepo to v19.6.0 (#12920) (151e0164)
|
||||||
|
* update postgres docker tag to v17.2 (#12931) (036f4564)
|
||||||
|
* update postgres docker tag to v17.1 (#12915) (0d0f9144)
|
||||||
|
* update dependency sass-embedded to v1.81.0 (#12914) (ff258028)
|
||||||
|
* update dependency sass-embedded to v1.80.6 (#12894) (37f877b3)
|
||||||
|
* update dependency sass-embedded to v1.80.5 (#12889) (383f332c)
|
||||||
|
* update dependency mocha to v10.8.2 (#12884) (d86bedf8)
|
||||||
|
* update coverallsapp/github-action action to v2.3.4 (#12867) (351bcdbf)
|
||||||
|
* update dependency sass-embedded to v1.80.4 (#12861) (be0c92ec)
|
||||||
|
* update dependency smtp-server to v3.13.6 (#12859) (a0804485)
|
||||||
|
* update coverallsapp/github-action action to v2.3.3 (#12854) (48b09f1d)
|
||||||
|
* update coverallsapp/github-action action to v2.3.2 (#12846) (c16192dc)
|
||||||
|
* update dependency sass-embedded to v1.79.5 (#12850) (6f227264)
|
||||||
|
* update dependency eslint-plugin-import to v2.31.0 (#12832) (cc2e0e9f)
|
||||||
|
* update redis docker tag to v7.4.1 (#12836) (e7cba341)
|
||||||
|
* update mongo docker tag to v8 (#12810) (2fd89558)
|
||||||
|
* update dependency sass-embedded to v1.79.4 (#12825) (24928b17)
|
||||||
|
* update postgres docker tag to v17 (#12829) (d7299f90)
|
||||||
|
|
||||||
|
##### New Features
|
||||||
|
|
||||||
|
* use displayname in chat system messages, closes #12937 (e6f78d25)
|
||||||
|
* move website/location fields into custom user fields (669c9c50)
|
||||||
|
* add pagination to groups page, api routes (49e0e1ab)
|
||||||
|
* add date and multiselect custom fields (9cf85ced)
|
||||||
|
* new language file for user custom fields (05fb8aa5)
|
||||||
|
* closes #12902, allow adding users as post editors (bc00df3c)
|
||||||
|
* added nn_NO, takk\! (b9eff72d)
|
||||||
|
* add read topics route (757d7101)
|
||||||
|
|
||||||
|
##### Bug Fixes
|
||||||
|
|
||||||
|
* update acp template to use new lang keys (bc5b91aa)
|
||||||
|
* spec, dont show registered-users, verified-users, unverified-users in manage groups (8605584f)
|
||||||
|
* #12927, give more right padding on FF (5b3b003d)
|
||||||
|
* dont show chat upload btn if maxFileSize is 0, #12926 (14125858)
|
||||||
|
* dont allow core user fields to be used as custom fields (836e8458)
|
||||||
|
* button to match category dropdown (5e9b7b44)
|
||||||
|
* filter undefined posts/topics (db291ecd)
|
||||||
|
* don't crash if post is undefined (d4770908)
|
||||||
|
* spec (d5c9c0ba)
|
||||||
|
* block search showing old matches (a9fc13dd)
|
||||||
|
* missing template (008ad1e3)
|
||||||
|
* naive checking to handle uploaded images whose filenames end with '-resized' (1268ed50)
|
||||||
|
* update copy to reflect real default value of 2000px for `resizeImageWidthThreshold` (4614958b)
|
||||||
|
* editor removal (977d80c1)
|
||||||
|
* add warning txt file for nn-NO (23b3a64a)
|
||||||
|
* incorrect folder syntax for nn_NO (396c7766)
|
||||||
|
* use postData.topic.title instead of topicData.title (d24bc5c5)
|
||||||
|
* another missing await (10a85e94)
|
||||||
|
* **deps:**
|
||||||
|
* update dependency sortablejs to v1.15.4 (#12934) (2db5b815)
|
||||||
|
* update dependency nodebb-plugin-2factor to v7.5.7 (#12930) (f9c4815a)
|
||||||
|
* update dependency @fortawesome/fontawesome-free to v6.7.1 (#12929) (80b4d10b)
|
||||||
|
* update dependency mongodb to v6.11.0 (#12932) (c3b42c85)
|
||||||
|
* update dependency @fortawesome/fontawesome-free to v6.7.0 (#12921) (a3042479)
|
||||||
|
* update dependency ace-builds to v1.36.5 (#12913) (68637633)
|
||||||
|
* update dependency sass to v1.81.0 (#12916) (ee91e516)
|
||||||
|
* update dependency cron to v3.2.1 (#12911) (42791287)
|
||||||
|
* update dependency postcss to v8.4.49 (#12910) (85f55695)
|
||||||
|
* update dependency winston to v3.17.0 (#12906) (87c276dd)
|
||||||
|
* update dependency postcss to v8.4.48 (#12907) (aa57b248)
|
||||||
|
* update dependency nodebb-theme-peace to v2.2.8 (#12901) (8af37a7d)
|
||||||
|
* update dependency cron to v3.1.9 (#12899) (87ec8637)
|
||||||
|
* update dependency ace-builds to v1.36.4 (#12898) (3aa0fa61)
|
||||||
|
* update dependency winston to v3.16.0 (#12896) (a4f75ca1)
|
||||||
|
* update dependency compression to v1.7.5 (#12890) (f5439d95)
|
||||||
|
* update dependency webpack to v5.96.1 (#12895) (3d847a5a)
|
||||||
|
* update dependency sass to v1.80.6 (#12892) (c5dd8b67)
|
||||||
|
* update dependency jquery-ui to v1.14.1 (#12891) (3fb6587a)
|
||||||
|
* update dependency nodebb-plugin-composer-default to v10.2.42 (#12888) (2cb2ac3a)
|
||||||
|
* update dependency cron to v3.1.8 (#12881) (8ae2f503)
|
||||||
|
* update dependency chart.js to v4.4.6 (#12876) (89bd1a24)
|
||||||
|
* update dependency nodemailer to v6.9.16 (#12874) (51cb249e)
|
||||||
|
* update dependency pg-cursor to v2.12.1 (#12870) (e3203b66)
|
||||||
|
* update dependency pg to v8.13.1 (#12869) (b628013d)
|
||||||
|
* update socket.io packages to v4.8.1 (#12871) (cdd9caa7)
|
||||||
|
* update dependency nodebb-theme-harmony to v1.2.77 (#12868) (d30e1f91)
|
||||||
|
* update dependency mongodb to v6.10.0 (#12862) (6da24583)
|
||||||
|
* update dependency ace-builds to v1.36.3 (#12860) (8d8243c4)
|
||||||
|
* update dependency sass to v1.80.4 (#12855) (80034ae8)
|
||||||
|
* update socket.io packages to v4.8.0 (#12815) (4788e2a3)
|
||||||
|
* update dependency chart.js to v4.4.5 (#12852) (c504b49f)
|
||||||
|
* update dependency workerpool to v9.2.0 (#12851) (8dfb2237)
|
||||||
|
* update dependency sass to v1.79.5 (#12847) (ab859aec)
|
||||||
|
* update dependency cookie-parser to v1.4.7 (#12842) (526faf07)
|
||||||
|
* update dependency express to v4.21.1 (#12843) (0e7f11bd)
|
||||||
|
* update dependency express-session to v1.18.1 (#12844) (38c1072f)
|
||||||
|
* update dependency sanitize-html to v2.13.1 (#12839) (5159552d)
|
||||||
|
* update dependency winston to v3.15.0 (#12840) (29826cf2)
|
||||||
|
* update dependency webpack to v5.95.0 (#12828) (55faa8a2)
|
||||||
|
* update dependency sass to v1.79.4 (#12826) (376ac905)
|
||||||
|
* update dependency helmet to v7.2.0 (#12827) (5acf0398)
|
||||||
|
* update dependency nodebb-theme-harmony to v1.2.71 (#12820) (b1993ff0)
|
||||||
|
|
||||||
|
##### Other Changes
|
||||||
|
|
||||||
|
* remove unused utils (0e4fc531)
|
||||||
|
* remove unused utils (b6a79365)
|
||||||
|
* //github.com/NodeBB/NodeBB/issues/12824 (c4a60dbb)
|
||||||
|
|
||||||
|
##### Refactors
|
||||||
|
|
||||||
|
* shorter check for showfullname (6ebff2e1)
|
||||||
|
* remove unused uids (20da7148)
|
||||||
|
* no category sharing for a long time (6af5cef7)
|
||||||
|
* move flags into core (3dff083d)
|
||||||
|
* add some margin bottom to bs tags (40758b32)
|
||||||
|
* post queue btn and dropdown (038fb71e)
|
||||||
|
* search logic to use switch..case (bd76ccf0)
|
||||||
|
|
||||||
|
##### Tests
|
||||||
|
|
||||||
|
* fix tests (173c604a)
|
||||||
|
* fix crash due to excludeGroups (61d43ee1)
|
||||||
|
* fix spec (70d99501)
|
||||||
|
* fix spec (c586854a)
|
||||||
|
* update spec on plugins (f9178a39)
|
||||||
|
|
||||||
#### v3.10.3 (2024-10-23)
|
#### v3.10.3 (2024-10-23)
|
||||||
|
|
||||||
##### Chores
|
##### Chores
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
version: '3.8'
|
|
||||||
|
|
||||||
services:
|
services:
|
||||||
nodebb:
|
nodebb:
|
||||||
build: .
|
build: .
|
||||||
|
|||||||
@@ -50,7 +50,7 @@
|
|||||||
"bootstrap": "5.3.3",
|
"bootstrap": "5.3.3",
|
||||||
"bootswatch": "5.3.3",
|
"bootswatch": "5.3.3",
|
||||||
"chalk": "4.1.2",
|
"chalk": "4.1.2",
|
||||||
"chart.js": "4.4.6",
|
"chart.js": "4.4.7",
|
||||||
"cli-graph": "3.2.2",
|
"cli-graph": "3.2.2",
|
||||||
"clipboard": "2.0.11",
|
"clipboard": "2.0.11",
|
||||||
"colors": "1.4.0",
|
"colors": "1.4.0",
|
||||||
@@ -69,7 +69,7 @@
|
|||||||
"daemon": "1.1.0",
|
"daemon": "1.1.0",
|
||||||
"diff": "7.0.0",
|
"diff": "7.0.0",
|
||||||
"esbuild": "0.24.0",
|
"esbuild": "0.24.0",
|
||||||
"express": "4.21.1",
|
"express": "4.21.2",
|
||||||
"express-session": "1.18.1",
|
"express-session": "1.18.1",
|
||||||
"express-useragent": "1.0.15",
|
"express-useragent": "1.0.15",
|
||||||
"fetch-cookie": "3.0.1",
|
"fetch-cookie": "3.0.1",
|
||||||
@@ -105,12 +105,12 @@
|
|||||||
"nodebb-plugin-emoji-android": "4.1.1",
|
"nodebb-plugin-emoji-android": "4.1.1",
|
||||||
"nodebb-plugin-markdown": "13.0.0-pre.10",
|
"nodebb-plugin-markdown": "13.0.0-pre.10",
|
||||||
"nodebb-plugin-mentions": "4.6.10",
|
"nodebb-plugin-mentions": "4.6.10",
|
||||||
"nodebb-plugin-spam-be-gone": "2.2.2",
|
"nodebb-plugin-spam-be-gone": "2.3.0",
|
||||||
"nodebb-plugin-web-push": "0.7.0",
|
"nodebb-plugin-web-push": "0.7.0",
|
||||||
"nodebb-rewards-essentials": "1.0.0",
|
"nodebb-rewards-essentials": "1.0.0",
|
||||||
"nodebb-theme-harmony": "2.0.0-pre.43",
|
"nodebb-theme-harmony": "2.0.0-pre.43",
|
||||||
"nodebb-theme-lavender": "7.1.16",
|
"nodebb-theme-lavender": "7.1.17",
|
||||||
"nodebb-theme-peace": "2.2.23",
|
"nodebb-theme-peace": "2.2.28",
|
||||||
"nodebb-theme-persona": "14.0.0-pre.5",
|
"nodebb-theme-persona": "14.0.0-pre.5",
|
||||||
"nodebb-widget-essentials": "7.0.31",
|
"nodebb-widget-essentials": "7.0.31",
|
||||||
"nodemailer": "6.9.16",
|
"nodemailer": "6.9.16",
|
||||||
@@ -129,7 +129,7 @@
|
|||||||
"rss": "1.2.2",
|
"rss": "1.2.2",
|
||||||
"rtlcss": "4.3.0",
|
"rtlcss": "4.3.0",
|
||||||
"sanitize-html": "2.13.1",
|
"sanitize-html": "2.13.1",
|
||||||
"sass": "1.81.0",
|
"sass": "1.82.0",
|
||||||
"satori": "^0.11.1",
|
"satori": "^0.11.1",
|
||||||
"semver": "7.6.3",
|
"semver": "7.6.3",
|
||||||
"serve-favicon": "2.5.0",
|
"serve-favicon": "2.5.0",
|
||||||
@@ -148,7 +148,7 @@
|
|||||||
"toobusy-js": "0.5.1",
|
"toobusy-js": "0.5.1",
|
||||||
"tough-cookie": "5.0.0",
|
"tough-cookie": "5.0.0",
|
||||||
"validator": "13.12.0",
|
"validator": "13.12.0",
|
||||||
"webpack": "5.96.1",
|
"webpack": "5.97.1",
|
||||||
"webpack-merge": "6.0.1",
|
"webpack-merge": "6.0.1",
|
||||||
"winston": "3.17.0",
|
"winston": "3.17.0",
|
||||||
"workerpool": "9.2.0",
|
"workerpool": "9.2.0",
|
||||||
@@ -169,15 +169,15 @@
|
|||||||
"grunt-contrib-watch": "1.1.0",
|
"grunt-contrib-watch": "1.1.0",
|
||||||
"husky": "8.0.3",
|
"husky": "8.0.3",
|
||||||
"jsdom": "25.0.1",
|
"jsdom": "25.0.1",
|
||||||
"lint-staged": "15.2.10",
|
"lint-staged": "15.2.11",
|
||||||
"mocha": "10.8.2",
|
"mocha": "11.0.1",
|
||||||
"mocha-lcov-reporter": "1.3.0",
|
"mocha-lcov-reporter": "1.3.0",
|
||||||
"mockdate": "3.0.5",
|
"mockdate": "3.0.5",
|
||||||
"nyc": "17.1.0",
|
"nyc": "17.1.0",
|
||||||
"smtp-server": "3.13.6"
|
"smtp-server": "3.13.6"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"sass-embedded": "1.81.0"
|
"sass-embedded": "1.82.0"
|
||||||
},
|
},
|
||||||
"resolutions": {
|
"resolutions": {
|
||||||
"*/jquery": "3.7.1"
|
"*/jquery": "3.7.1"
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
const winston = require('winston');
|
const winston = require('winston');
|
||||||
const express = require('express');
|
const express = require('express');
|
||||||
|
const session = require('express-session');
|
||||||
const bodyParser = require('body-parser');
|
const bodyParser = require('body-parser');
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
@@ -13,7 +14,10 @@ const nconf = require('nconf');
|
|||||||
const Benchpress = require('benchpressjs');
|
const Benchpress = require('benchpressjs');
|
||||||
const { mkdirp } = require('mkdirp');
|
const { mkdirp } = require('mkdirp');
|
||||||
const { paths } = require('../src/constants');
|
const { paths } = require('../src/constants');
|
||||||
const sass = require('../src/utils').getSass();
|
const utils = require('../src/utils');
|
||||||
|
|
||||||
|
const sass = utils.getSass();
|
||||||
|
const { generateToken, csrfSynchronisedProtection } = require('../src/middleware/csrf');
|
||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
let server;
|
let server;
|
||||||
@@ -73,6 +77,13 @@ web.install = async function (port) {
|
|||||||
app.use(bodyParser.urlencoded({
|
app.use(bodyParser.urlencoded({
|
||||||
extended: true,
|
extended: true,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
app.use(session({
|
||||||
|
secret: utils.generateUUID(),
|
||||||
|
resave: false,
|
||||||
|
saveUninitialized: false,
|
||||||
|
}));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
compileTemplate(),
|
compileTemplate(),
|
||||||
@@ -103,8 +114,8 @@ function launchExpress(port) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function setupRoutes() {
|
function setupRoutes() {
|
||||||
app.get('/', welcome);
|
app.get('/', csrfSynchronisedProtection, welcome);
|
||||||
app.post('/', install);
|
app.post('/', csrfSynchronisedProtection, install);
|
||||||
app.get('/testdb', testDatabase);
|
app.get('/testdb', testDatabase);
|
||||||
app.get('/ping', ping);
|
app.get('/ping', ping);
|
||||||
app.get('/sping', ping);
|
app.get('/sping', ping);
|
||||||
@@ -160,6 +171,7 @@ function welcome(req, res) {
|
|||||||
minimumPasswordStrength: defaults.minimumPasswordStrength,
|
minimumPasswordStrength: defaults.minimumPasswordStrength,
|
||||||
installing: installing,
|
installing: installing,
|
||||||
percentInstalled: installing ? ((Date.now() - timeStart) / totalTime * 100).toFixed(2) : 0,
|
percentInstalled: installing ? ((Date.now() - timeStart) / totalTime * 100).toFixed(2) : 0,
|
||||||
|
csrf_token: generateToken(req),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"post-sort-option": "帖子分类选项,1%",
|
"post-sort-option": "帖子分类选项,%1",
|
||||||
"topic-sort-option": "主题分类选项,%1",
|
"topic-sort-option": "主题分类选项,%1",
|
||||||
"user-avatar-for": "用户头像%1",
|
"user-avatar-for": "用户头像%1",
|
||||||
"user-watched-tags": "用户关注标签",
|
"user-watched-tags": "用户关注标签",
|
||||||
|
|||||||
@@ -38,8 +38,8 @@
|
|||||||
"chat.no-pinned-messages": "这里没有已置顶消息",
|
"chat.no-pinned-messages": "这里没有已置顶消息",
|
||||||
"chat.pin-message": "置顶消息",
|
"chat.pin-message": "置顶消息",
|
||||||
"chat.unpin-message": "取消置顶消息",
|
"chat.unpin-message": "取消置顶消息",
|
||||||
"chat.public-rooms": "公开房间(1%)",
|
"chat.public-rooms": "公开房间(%1)",
|
||||||
"chat.private-rooms": "私有房间(1%)",
|
"chat.private-rooms": "私有房间(%1)",
|
||||||
"chat.create-room": "创建聊天室",
|
"chat.create-room": "创建聊天室",
|
||||||
"chat.private.option": "私有(仅已加入房间用户可见)",
|
"chat.private.option": "私有(仅已加入房间用户可见)",
|
||||||
"chat.public.option": "公开(对选中组里的所有用户可见)",
|
"chat.public.option": "公开(对选中组里的所有用户可见)",
|
||||||
|
|||||||
@@ -117,9 +117,9 @@
|
|||||||
"thread-tools.purge-confirm": "确认清除此主题吗?",
|
"thread-tools.purge-confirm": "确认清除此主题吗?",
|
||||||
"thread-tools.merge-topics": "合并主题",
|
"thread-tools.merge-topics": "合并主题",
|
||||||
"thread-tools.merge": "合并主题",
|
"thread-tools.merge": "合并主题",
|
||||||
"topic-move-success": "注意:此主题将会被移动到“1%”。点击此处可取消。",
|
"topic-move-success": "注意:此主题将会被移动到“%1”。点击此处可取消。",
|
||||||
"topic-move-multiple-success": "注意:以下主题将会被移动到“%1”,点击此处可取消。",
|
"topic-move-multiple-success": "注意:以下主题将会被移动到“%1”,点击此处可取消。",
|
||||||
"topic-move-all-success": "注意 :全部主题都将被移动到“1%”,点击此处可取消。",
|
"topic-move-all-success": "注意 :全部主题都将被移动到“%1”,点击此处可取消。",
|
||||||
"topic-move-undone": "撤销主题移动",
|
"topic-move-undone": "撤销主题移动",
|
||||||
"topic-move-posts-success": "此帖子将马上移动。点击此处撤销",
|
"topic-move-posts-success": "此帖子将马上移动。点击此处撤销",
|
||||||
"topic-move-posts-undone": "撤销帖子移动",
|
"topic-move-posts-undone": "撤销帖子移动",
|
||||||
@@ -170,7 +170,7 @@
|
|||||||
"composer.schedule": "定时",
|
"composer.schedule": "定时",
|
||||||
"composer.replying-to": "正在回复 %1",
|
"composer.replying-to": "正在回复 %1",
|
||||||
"composer.new-topic": "新主题",
|
"composer.new-topic": "新主题",
|
||||||
"composer.editing-in": "在 1% 中编辑帖子",
|
"composer.editing-in": "在 %1 中编辑帖子",
|
||||||
"composer.uploading": "正在上传...",
|
"composer.uploading": "正在上传...",
|
||||||
"composer.thumb-url-label": "粘贴主题缩略图网址",
|
"composer.thumb-url-label": "粘贴主题缩略图网址",
|
||||||
"composer.thumb-title": "给此主题添加缩略图",
|
"composer.thumb-title": "给此主题添加缩略图",
|
||||||
|
|||||||
@@ -80,20 +80,38 @@ paths:
|
|||||||
$ref: 'read/admin/dashboard/topics.yaml'
|
$ref: 'read/admin/dashboard/topics.yaml'
|
||||||
/api/admin/dashboard/searches:
|
/api/admin/dashboard/searches:
|
||||||
$ref: 'read/admin/dashboard/searches.yaml'
|
$ref: 'read/admin/dashboard/searches.yaml'
|
||||||
"/api/admin/settings/{term}":
|
"/api/admin/settings/general":
|
||||||
$ref: 'read/admin/settings/term.yaml'
|
$ref: 'read/admin/settings/general.yaml'
|
||||||
/api/admin/settings/navigation:
|
/api/admin/settings/navigation:
|
||||||
$ref: 'read/admin/settings/navigation.yaml'
|
$ref: 'read/admin/settings/navigation.yaml'
|
||||||
/api/admin/settings/api:
|
|
||||||
$ref: 'read/admin/settings/api.yaml'
|
|
||||||
/api/admin/settings/email:
|
|
||||||
$ref: 'read/admin/settings/email.yaml'
|
|
||||||
/api/admin/settings/user:
|
/api/admin/settings/user:
|
||||||
$ref: 'read/admin/settings/user.yaml'
|
$ref: 'read/admin/settings/user.yaml'
|
||||||
|
/api/admin/settings/reputation:
|
||||||
|
$ref: 'read/admin/settings/reputation.yaml'
|
||||||
|
/api/admin/settings/group:
|
||||||
|
$ref: 'read/admin/settings/group.yaml'
|
||||||
|
/api/admin/settings/tags:
|
||||||
|
$ref: 'read/admin/settings/tags.yaml'
|
||||||
/api/admin/settings/post:
|
/api/admin/settings/post:
|
||||||
$ref: 'read/admin/settings/post.yaml'
|
$ref: 'read/admin/settings/post.yaml'
|
||||||
/api/admin/settings/activitypub:
|
/api/admin/settings/activitypub:
|
||||||
$ref: 'read/admin/settings/activitypub.yaml'
|
$ref: 'read/admin/settings/activitypub.yaml'
|
||||||
|
/api/admin/settings/uploads:
|
||||||
|
$ref: 'read/admin/settings/uploads.yaml'
|
||||||
|
/api/admin/settings/email:
|
||||||
|
$ref: 'read/admin/settings/email.yaml'
|
||||||
|
/api/admin/settings/chat:
|
||||||
|
$ref: 'read/admin/settings/chat.yaml'
|
||||||
|
/api/admin/settings/pagination:
|
||||||
|
$ref: 'read/admin/settings/pagination.yaml'
|
||||||
|
/api/admin/settings/notifications:
|
||||||
|
$ref: 'read/admin/settings/notifications.yaml'
|
||||||
|
/api/admin/settings/api:
|
||||||
|
$ref: 'read/admin/settings/api.yaml'
|
||||||
|
/api/admin/settings/cookies:
|
||||||
|
$ref: 'read/admin/settings/cookies.yaml'
|
||||||
|
/api/admin/settings/web-crawler:
|
||||||
|
$ref: 'read/admin/settings/web-crawler.yaml'
|
||||||
/api/admin/settings/advanced:
|
/api/admin/settings/advanced:
|
||||||
$ref: 'read/admin/settings/advanced.yaml'
|
$ref: 'read/admin/settings/advanced.yaml'
|
||||||
/api/admin/manage/categories:
|
/api/admin/manage/categories:
|
||||||
@@ -124,8 +142,12 @@ paths:
|
|||||||
$ref: 'read/admin/manage/uploads.yaml'
|
$ref: 'read/admin/manage/uploads.yaml'
|
||||||
/api/admin/manage/digest:
|
/api/admin/manage/digest:
|
||||||
$ref: 'read/admin/manage/digest.yaml'
|
$ref: 'read/admin/manage/digest.yaml'
|
||||||
"/api/admin/appearance/{term}":
|
"/api/admin/appearance/themes":
|
||||||
$ref: 'read/admin/appearance/term.yaml'
|
$ref: 'read/admin/appearance/themes.yaml'
|
||||||
|
"/api/admin/appearance/skins":
|
||||||
|
$ref: 'read/admin/appearance/skins.yaml'
|
||||||
|
"/api/admin/appearance/customise":
|
||||||
|
$ref: 'read/admin/appearance/customise.yaml'
|
||||||
/api/admin/extend/plugins:
|
/api/admin/extend/plugins:
|
||||||
$ref: 'read/admin/extend/plugins.yaml'
|
$ref: 'read/admin/extend/plugins.yaml'
|
||||||
/api/admin/extend/widgets:
|
/api/admin/extend/widgets:
|
||||||
|
|||||||
@@ -2,13 +2,6 @@ get:
|
|||||||
tags:
|
tags:
|
||||||
- admin
|
- admin
|
||||||
summary: Get appearance settings
|
summary: Get appearance settings
|
||||||
parameters:
|
|
||||||
- name: term
|
|
||||||
in: path
|
|
||||||
required: true
|
|
||||||
schema:
|
|
||||||
type: string
|
|
||||||
example: themes
|
|
||||||
responses:
|
responses:
|
||||||
"200":
|
"200":
|
||||||
description: ""
|
description: ""
|
||||||
11
public/openapi/read/admin/appearance/skins.yaml
Normal file
11
public/openapi/read/admin/appearance/skins.yaml
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- admin
|
||||||
|
summary: Get appearance settings
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: ""
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: ../../../components/schemas/CommonProps.yaml#/CommonProps
|
||||||
11
public/openapi/read/admin/appearance/themes.yaml
Normal file
11
public/openapi/read/admin/appearance/themes.yaml
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- admin
|
||||||
|
summary: Get appearance settings
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: ""
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: ../../../components/schemas/CommonProps.yaml#/CommonProps
|
||||||
16
public/openapi/read/admin/settings/chat.yaml
Normal file
16
public/openapi/read/admin/settings/chat.yaml
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- admin
|
||||||
|
summary: Get chat settings
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: ""
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
allOf:
|
||||||
|
- type: object
|
||||||
|
properties:
|
||||||
|
title:
|
||||||
|
type: string
|
||||||
|
- $ref: ../../../components/schemas/CommonProps.yaml#/CommonProps
|
||||||
16
public/openapi/read/admin/settings/cookies.yaml
Normal file
16
public/openapi/read/admin/settings/cookies.yaml
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- admin
|
||||||
|
summary: Get cookie settings
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: ""
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
allOf:
|
||||||
|
- type: object
|
||||||
|
properties:
|
||||||
|
title:
|
||||||
|
type: string
|
||||||
|
- $ref: ../../../components/schemas/CommonProps.yaml#/CommonProps
|
||||||
@@ -2,13 +2,6 @@ get:
|
|||||||
tags:
|
tags:
|
||||||
- admin
|
- admin
|
||||||
summary: Get system settings
|
summary: Get system settings
|
||||||
parameters:
|
|
||||||
- name: term
|
|
||||||
in: path
|
|
||||||
required: true
|
|
||||||
schema:
|
|
||||||
type: string
|
|
||||||
example: general
|
|
||||||
responses:
|
responses:
|
||||||
"200":
|
"200":
|
||||||
description: ""
|
description: ""
|
||||||
16
public/openapi/read/admin/settings/group.yaml
Normal file
16
public/openapi/read/admin/settings/group.yaml
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- admin
|
||||||
|
summary: Get group settings
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: ""
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
allOf:
|
||||||
|
- type: object
|
||||||
|
properties:
|
||||||
|
title:
|
||||||
|
type: string
|
||||||
|
- $ref: ../../../components/schemas/CommonProps.yaml#/CommonProps
|
||||||
16
public/openapi/read/admin/settings/notifications.yaml
Normal file
16
public/openapi/read/admin/settings/notifications.yaml
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- admin
|
||||||
|
summary: Get notification settings
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: ""
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
allOf:
|
||||||
|
- type: object
|
||||||
|
properties:
|
||||||
|
title:
|
||||||
|
type: string
|
||||||
|
- $ref: ../../../components/schemas/CommonProps.yaml#/CommonProps
|
||||||
16
public/openapi/read/admin/settings/pagination.yaml
Normal file
16
public/openapi/read/admin/settings/pagination.yaml
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- admin
|
||||||
|
summary: Get pagination settings
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: ""
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
allOf:
|
||||||
|
- type: object
|
||||||
|
properties:
|
||||||
|
title:
|
||||||
|
type: string
|
||||||
|
- $ref: ../../../components/schemas/CommonProps.yaml#/CommonProps
|
||||||
16
public/openapi/read/admin/settings/reputation.yaml
Normal file
16
public/openapi/read/admin/settings/reputation.yaml
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- admin
|
||||||
|
summary: Get reputation settings
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: ""
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
allOf:
|
||||||
|
- type: object
|
||||||
|
properties:
|
||||||
|
title:
|
||||||
|
type: string
|
||||||
|
- $ref: ../../../components/schemas/CommonProps.yaml#/CommonProps
|
||||||
16
public/openapi/read/admin/settings/tags.yaml
Normal file
16
public/openapi/read/admin/settings/tags.yaml
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- admin
|
||||||
|
summary: Get tag settings
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: ""
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
allOf:
|
||||||
|
- type: object
|
||||||
|
properties:
|
||||||
|
title:
|
||||||
|
type: string
|
||||||
|
- $ref: ../../../components/schemas/CommonProps.yaml#/CommonProps
|
||||||
16
public/openapi/read/admin/settings/uploads.yaml
Normal file
16
public/openapi/read/admin/settings/uploads.yaml
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- admin
|
||||||
|
summary: Get upload settings
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: ""
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
allOf:
|
||||||
|
- type: object
|
||||||
|
properties:
|
||||||
|
title:
|
||||||
|
type: string
|
||||||
|
- $ref: ../../../components/schemas/CommonProps.yaml#/CommonProps
|
||||||
16
public/openapi/read/admin/settings/web-crawler.yaml
Normal file
16
public/openapi/read/admin/settings/web-crawler.yaml
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- admin
|
||||||
|
summary: Get web crawler settings
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: ""
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
allOf:
|
||||||
|
- type: object
|
||||||
|
properties:
|
||||||
|
title:
|
||||||
|
type: string
|
||||||
|
- $ref: ../../../components/schemas/CommonProps.yaml#/CommonProps
|
||||||
@@ -32,13 +32,16 @@ define('forum/users', [
|
|||||||
|
|
||||||
// Populate box with query if present
|
// Populate box with query if present
|
||||||
const searchEl = document.getElementById('search-user');
|
const searchEl = document.getElementById('search-user');
|
||||||
|
if (searchEl) {
|
||||||
const search = new URLSearchParams(document.location.search);
|
const search = new URLSearchParams(document.location.search);
|
||||||
const query = search.get('query');
|
const query = search.get('query');
|
||||||
if (query) {
|
if (query) {
|
||||||
searchEl.value = query;
|
searchEl.value = query;
|
||||||
}
|
}
|
||||||
|
if (!utils.isMobile()) {
|
||||||
searchEl.focus();
|
searchEl.focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
function doSearch() {
|
function doSearch() {
|
||||||
|
|||||||
@@ -67,8 +67,8 @@ usersAPI.update = async function (caller, data) {
|
|||||||
privileges.users.canEdit(caller.uid, data.uid),
|
privileges.users.canEdit(caller.uid, data.uid),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Changing own email/username requires password confirmation
|
const isChangingEmailOrUsername = data.hasOwnProperty('email') || data.hasOwnProperty('username');
|
||||||
if (data.hasOwnProperty('email') || data.hasOwnProperty('username')) {
|
if (isChangingEmailOrUsername) {
|
||||||
await isPrivilegedOrSelfAndPasswordMatch(caller, data);
|
await isPrivilegedOrSelfAndPasswordMatch(caller, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -531,7 +531,6 @@ async function processDeletion({ uid, method, password, caller }) {
|
|||||||
throw new Error('[[error:no-privileges]]');
|
throw new Error('[[error:no-privileges]]');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Self-deletions require a password
|
|
||||||
const hasPassword = await user.hasPassword(uid);
|
const hasPassword = await user.hasPassword(uid);
|
||||||
if (isSelf && hasPassword) {
|
if (isSelf && hasPassword) {
|
||||||
const ok = await user.isPasswordCorrect(uid, password, caller.ip);
|
const ok = await user.isPasswordCorrect(uid, password, caller.ip);
|
||||||
|
|||||||
10
src/cache/lru.js
vendored
10
src/cache/lru.js
vendored
@@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
module.exports = function (opts) {
|
module.exports = function (opts) {
|
||||||
const { LRUCache } = require('lru-cache');
|
const { LRUCache } = require('lru-cache');
|
||||||
|
const os = require('os');
|
||||||
|
|
||||||
const pubsub = require('../pubsub');
|
const pubsub = require('../pubsub');
|
||||||
|
|
||||||
// lru-cache@7 deprecations
|
// lru-cache@7 deprecations
|
||||||
@@ -99,7 +101,9 @@ module.exports = function (opts) {
|
|||||||
cache.delete = cache.del;
|
cache.delete = cache.del;
|
||||||
|
|
||||||
cache.reset = function () {
|
cache.reset = function () {
|
||||||
pubsub.publish(`${cache.name}:lruCache:reset`);
|
pubsub.publish(`${cache.name}:lruCache:reset`, {
|
||||||
|
id: `${os.hostname()}:${process.pid}`,
|
||||||
|
});
|
||||||
localReset();
|
localReset();
|
||||||
};
|
};
|
||||||
cache.clear = cache.reset;
|
cache.clear = cache.reset;
|
||||||
@@ -110,8 +114,10 @@ module.exports = function (opts) {
|
|||||||
cache.misses = 0;
|
cache.misses = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
pubsub.on(`${cache.name}:lruCache:reset`, () => {
|
pubsub.on(`${cache.name}:lruCache:reset`, ({ id }) => {
|
||||||
|
if (id !== `${os.hostname()}:${process.pid}`) {
|
||||||
localReset();
|
localReset();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
pubsub.on(`${cache.name}:lruCache:del`, (keys) => {
|
pubsub.on(`${cache.name}:lruCache:del`, (keys) => {
|
||||||
|
|||||||
10
src/cache/ttl.js
vendored
10
src/cache/ttl.js
vendored
@@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
module.exports = function (opts) {
|
module.exports = function (opts) {
|
||||||
const TTLCache = require('@isaacs/ttlcache');
|
const TTLCache = require('@isaacs/ttlcache');
|
||||||
|
const os = require('os');
|
||||||
|
|
||||||
const pubsub = require('../pubsub');
|
const pubsub = require('../pubsub');
|
||||||
|
|
||||||
const ttlCache = new TTLCache(opts);
|
const ttlCache = new TTLCache(opts);
|
||||||
@@ -72,7 +74,9 @@ module.exports = function (opts) {
|
|||||||
cache.delete = cache.del;
|
cache.delete = cache.del;
|
||||||
|
|
||||||
cache.reset = function () {
|
cache.reset = function () {
|
||||||
pubsub.publish(`${cache.name}:ttlCache:reset`);
|
pubsub.publish(`${cache.name}:ttlCache:reset`, {
|
||||||
|
id: `${os.hostname()}:${process.pid}`,
|
||||||
|
});
|
||||||
localReset();
|
localReset();
|
||||||
};
|
};
|
||||||
cache.clear = cache.reset;
|
cache.clear = cache.reset;
|
||||||
@@ -83,8 +87,10 @@ module.exports = function (opts) {
|
|||||||
cache.misses = 0;
|
cache.misses = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
pubsub.on(`${cache.name}:ttlCache:reset`, () => {
|
pubsub.on(`${cache.name}:ttlCache:reset`, ({ id }) => {
|
||||||
|
if (id !== `${os.hostname()}:${process.pid}`) {
|
||||||
localReset();
|
localReset();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
pubsub.on(`${cache.name}:ttlCache:del`, (keys) => {
|
pubsub.on(`${cache.name}:ttlCache:del`, (keys) => {
|
||||||
|
|||||||
@@ -165,7 +165,7 @@ module.exports = function (Categories) {
|
|||||||
Categories.assignColours = function () {
|
Categories.assignColours = function () {
|
||||||
const backgrounds = ['#AB4642', '#DC9656', '#F7CA88', '#A1B56C', '#86C1B9', '#7CAFC2', '#BA8BAF', '#A16946'];
|
const backgrounds = ['#AB4642', '#DC9656', '#F7CA88', '#A1B56C', '#86C1B9', '#7CAFC2', '#BA8BAF', '#A16946'];
|
||||||
const text = ['#ffffff', '#ffffff', '#333333', '#ffffff', '#333333', '#ffffff', '#ffffff', '#ffffff'];
|
const text = ['#ffffff', '#ffffff', '#333333', '#ffffff', '#333333', '#ffffff', '#ffffff', '#ffffff'];
|
||||||
const index = Math.floor(Math.random() * backgrounds.length);
|
const index = utils.secureRandom(0, backgrounds.length - 1);
|
||||||
return [backgrounds[index], text[index]];
|
return [backgrounds[index], text[index]];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -51,8 +51,8 @@ async function setup(initConfig) {
|
|||||||
}
|
}
|
||||||
console.log('NodeBB Setup Completed. Run "./nodebb start" to manually start your NodeBB server.');
|
console.log('NodeBB Setup Completed. Run "./nodebb start" to manually start your NodeBB server.');
|
||||||
|
|
||||||
// If I am a child process, notify the parent of the returned data before exiting (useful for notifying
|
// If I am a child process, notify the parent of the returned data before exiting
|
||||||
// hosts of auto-generated username/password during headless setups)
|
// (useful for notifying hosts during headless setups)
|
||||||
if (process.send) {
|
if (process.send) {
|
||||||
process.send(data);
|
process.send(data);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ const meta = require('../meta');
|
|||||||
const plugins = require('../plugins');
|
const plugins = require('../plugins');
|
||||||
const middleware = require('../middleware');
|
const middleware = require('../middleware');
|
||||||
const helpers = require('../middleware/helpers');
|
const helpers = require('../middleware/helpers');
|
||||||
|
const { secureRandom } = require('../utils');
|
||||||
|
|
||||||
exports.handle404 = helpers.try(async (req, res) => {
|
exports.handle404 = helpers.try(async (req, res) => {
|
||||||
const relativePath = nconf.get('relative_path');
|
const relativePath = nconf.get('relative_path');
|
||||||
@@ -64,6 +65,6 @@ exports.send404 = helpers.try(async (req, res) => {
|
|||||||
path: validator.escape(path),
|
path: validator.escape(path),
|
||||||
title: '[[global:404.title]]',
|
title: '[[global:404.title]]',
|
||||||
bodyClass: helpers.buildBodyClass(req, res),
|
bodyClass: helpers.buildBodyClass(req, res),
|
||||||
icon: icons[Math.floor(Math.random() * icons.length)],
|
icon: icons[secureRandom(0, icons.length - 1)],
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,11 +1,15 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
const nconf = require('nconf');
|
||||||
|
|
||||||
const user = require('../../user');
|
const user = require('../../user');
|
||||||
const helpers = require('../helpers');
|
const helpers = require('../helpers');
|
||||||
const pagination = require('../../pagination');
|
const pagination = require('../../pagination');
|
||||||
|
|
||||||
const followController = module.exports;
|
const followController = module.exports;
|
||||||
|
|
||||||
|
const url = nconf.get('url');
|
||||||
|
|
||||||
followController.getFollowing = async function (req, res, next) {
|
followController.getFollowing = async function (req, res, next) {
|
||||||
await getFollow('account/following', 'following', req, res, next);
|
await getFollow('account/following', 'following', req, res, next);
|
||||||
};
|
};
|
||||||
@@ -39,5 +43,12 @@ async function getFollow(tpl, name, req, res, next) {
|
|||||||
|
|
||||||
payload.breadcrumbs = helpers.buildBreadcrumbs([{ text: username, url: `/user/${userslug}` }, { text: `[[user:${name}]]` }]);
|
payload.breadcrumbs = helpers.buildBreadcrumbs([{ text: username, url: `/user/${userslug}` }, { text: `[[user:${name}]]` }]);
|
||||||
|
|
||||||
|
res.locals.linkTags = [
|
||||||
|
{
|
||||||
|
rel: 'canonical',
|
||||||
|
href: `${url}${req.url.replace(/^\/api/, '')}`,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
res.render(tpl, payload);
|
res.render(tpl, payload);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,15 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
const nconf = require('nconf');
|
||||||
|
|
||||||
const user = require('../../user');
|
const user = require('../../user');
|
||||||
const groups = require('../../groups');
|
const groups = require('../../groups');
|
||||||
const helpers = require('../helpers');
|
const helpers = require('../helpers');
|
||||||
|
|
||||||
const groupsController = module.exports;
|
const groupsController = module.exports;
|
||||||
|
|
||||||
|
const url = nconf.get('url');
|
||||||
|
|
||||||
groupsController.get = async function (req, res) {
|
groupsController.get = async function (req, res) {
|
||||||
const { username, userslug } = await user.getUserFields(res.locals.uid, ['username', 'userslug']);
|
const { username, userslug } = await user.getUserFields(res.locals.uid, ['username', 'userslug']);
|
||||||
|
|
||||||
@@ -21,5 +25,11 @@ groupsController.get = async function (req, res) {
|
|||||||
payload.groups = groupsData;
|
payload.groups = groupsData;
|
||||||
payload.title = `[[pages:account/groups, ${username}]]`;
|
payload.title = `[[pages:account/groups, ${username}]]`;
|
||||||
payload.breadcrumbs = helpers.buildBreadcrumbs([{ text: username, url: `/user/${userslug}` }, { text: '[[global:header.groups]]' }]);
|
payload.breadcrumbs = helpers.buildBreadcrumbs([{ text: username, url: `/user/${userslug}` }, { text: '[[global:header.groups]]' }]);
|
||||||
|
res.locals.linkTags = [
|
||||||
|
{
|
||||||
|
rel: 'canonical',
|
||||||
|
href: `${url}${req.url.replace(/^\/api/, '')}`,
|
||||||
|
},
|
||||||
|
];
|
||||||
res.render('account/groups', payload);
|
res.render('account/groups', payload);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
const nconf = require('nconf');
|
||||||
|
|
||||||
const db = require('../../database');
|
const db = require('../../database');
|
||||||
const user = require('../../user');
|
const user = require('../../user');
|
||||||
const posts = require('../../posts');
|
const posts = require('../../posts');
|
||||||
@@ -13,6 +15,8 @@ const utils = require('../../utils');
|
|||||||
|
|
||||||
const postsController = module.exports;
|
const postsController = module.exports;
|
||||||
|
|
||||||
|
const url = nconf.get('url');
|
||||||
|
|
||||||
const templateToData = {
|
const templateToData = {
|
||||||
'account/bookmarks': {
|
'account/bookmarks': {
|
||||||
type: 'posts',
|
type: 'posts',
|
||||||
@@ -247,6 +251,13 @@ async function getPostsFromUserSet(template, req, res) {
|
|||||||
option.selected = option.url.includes(`sort=${req.query.sort}`);
|
option.selected = option.url.includes(`sort=${req.query.sort}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
res.locals.linkTags = [
|
||||||
|
{
|
||||||
|
rel: 'canonical',
|
||||||
|
href: `${url}${req.url.replace(/^\/api/, '')}`,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
res.render(template, payload);
|
res.render(template, payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ const utils = require('../../utils');
|
|||||||
|
|
||||||
const profileController = module.exports;
|
const profileController = module.exports;
|
||||||
|
|
||||||
|
const url = nconf.get('url');
|
||||||
|
|
||||||
profileController.get = async function (req, res, next) {
|
profileController.get = async function (req, res, next) {
|
||||||
const { userData } = res.locals;
|
const { userData } = res.locals;
|
||||||
if (!userData) {
|
if (!userData) {
|
||||||
@@ -159,11 +161,18 @@ function addTags(res, userData) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
res.locals.linkTags = [];
|
||||||
|
|
||||||
|
res.locals.linkTags.push({
|
||||||
|
rel: 'canonical',
|
||||||
|
href: `${url}/user/${userData.userslug}`,
|
||||||
|
});
|
||||||
|
|
||||||
if (meta.config.activitypubEnabled) {
|
if (meta.config.activitypubEnabled) {
|
||||||
res.locals.linkTags = [{
|
res.locals.linkTags.push({
|
||||||
rel: 'alternate',
|
rel: 'alternate',
|
||||||
type: 'application/activity+json',
|
type: 'application/activity+json',
|
||||||
href: `${nconf.get('url')}/uid/${userData.uid}`,
|
href: `${nconf.get('url')}/uid/${userData.uid}`,
|
||||||
}];
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,14 @@
|
|||||||
|
|
||||||
const appearanceController = module.exports;
|
const appearanceController = module.exports;
|
||||||
|
|
||||||
appearanceController.get = function (req, res) {
|
appearanceController.themes = function (req, res) {
|
||||||
const term = req.params.term ? req.params.term : 'themes';
|
res.render(`admin/appearance/themes`, {});
|
||||||
|
};
|
||||||
res.render(`admin/appearance/${term}`, {});
|
|
||||||
|
appearanceController.skins = function (req, res) {
|
||||||
|
res.render(`admin/appearance/skins`, {});
|
||||||
|
};
|
||||||
|
|
||||||
|
appearanceController.customise = function (req, res) {
|
||||||
|
res.render(`admin/appearance/customise`, {});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ cacheController.dump = async function (req, res, next) {
|
|||||||
local: require('../../cache'),
|
local: require('../../cache'),
|
||||||
};
|
};
|
||||||
caches = await plugins.hooks.fire('filter:admin.cache.get', caches);
|
caches = await plugins.hooks.fire('filter:admin.cache.get', caches);
|
||||||
if (!caches[req.query.name]) {
|
if (!caches.hasOwnProperty(req.query.name)) {
|
||||||
return next();
|
return next();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -275,7 +275,7 @@ dashboardController.getLogins = async (req, res) => {
|
|||||||
|
|
||||||
res.render('admin/dashboard/logins', {
|
res.render('admin/dashboard/logins', {
|
||||||
set: 'logins',
|
set: 'logins',
|
||||||
query: req.query,
|
query: _.pick(req.query, ['units', 'until', 'count']),
|
||||||
stats,
|
stats,
|
||||||
summary,
|
summary,
|
||||||
sessions,
|
sessions,
|
||||||
@@ -303,7 +303,7 @@ dashboardController.getUsers = async (req, res) => {
|
|||||||
|
|
||||||
res.render('admin/dashboard/users', {
|
res.render('admin/dashboard/users', {
|
||||||
set: 'registrations',
|
set: 'registrations',
|
||||||
query: req.query,
|
query: _.pick(req.query, ['units', 'until', 'count']),
|
||||||
stats,
|
stats,
|
||||||
summary,
|
summary,
|
||||||
users,
|
users,
|
||||||
@@ -330,7 +330,7 @@ dashboardController.getTopics = async (req, res) => {
|
|||||||
|
|
||||||
res.render('admin/dashboard/topics', {
|
res.render('admin/dashboard/topics', {
|
||||||
set: 'topics',
|
set: 'topics',
|
||||||
query: req.query,
|
query: _.pick(req.query, ['units', 'until', 'count']),
|
||||||
stats,
|
stats,
|
||||||
summary,
|
summary,
|
||||||
topics: topicData,
|
topics: topicData,
|
||||||
|
|||||||
@@ -17,64 +17,20 @@ const translator = require('../../translator');
|
|||||||
|
|
||||||
const settingsController = module.exports;
|
const settingsController = module.exports;
|
||||||
|
|
||||||
settingsController.get = async function (req, res) {
|
settingsController.general = async (req, res) => {
|
||||||
const term = req.params.term || 'general';
|
const routes = await helpers.getHomePageRoutes(req.uid);
|
||||||
const payload = {
|
const postSharing = await social.getPostSharing();
|
||||||
title: `[[admin/menu:settings/${term}]]`,
|
|
||||||
};
|
|
||||||
if (term === 'general') {
|
|
||||||
payload.routes = await helpers.getHomePageRoutes(req.uid);
|
|
||||||
payload.postSharing = await social.getPostSharing();
|
|
||||||
const languageData = await languages.list();
|
const languageData = await languages.list();
|
||||||
languageData.forEach((language) => {
|
languageData.forEach((language) => {
|
||||||
language.selected = language.code === meta.config.defaultLang;
|
language.selected = language.code === meta.config.defaultLang;
|
||||||
});
|
});
|
||||||
payload.languages = languageData;
|
|
||||||
payload.autoDetectLang = meta.config.autoDetectLang;
|
|
||||||
}
|
|
||||||
res.render(`admin/settings/${term}`, payload);
|
|
||||||
};
|
|
||||||
|
|
||||||
settingsController.email = async (req, res) => {
|
res.render('admin/settings/general', {
|
||||||
const emails = await emailer.getTemplates(meta.config);
|
title: `[[admin/menu:settings/general]]`,
|
||||||
|
routes,
|
||||||
res.render('admin/settings/email', {
|
postSharing,
|
||||||
title: '[[admin/menu:settings/email]]',
|
languages: languageData,
|
||||||
emails: emails,
|
autoDetectLang: meta.config.autoDetectLang,
|
||||||
sendable: emails.filter(e => !e.path.includes('_plaintext') && !e.path.includes('partials')).map(tpl => tpl.path),
|
|
||||||
services: emailer.listServices(),
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
settingsController.user = async (req, res) => {
|
|
||||||
const [notificationTypes, groupData] = await Promise.all([
|
|
||||||
notifications.getAllNotificationTypes(),
|
|
||||||
groups.getNonPrivilegeGroups('groups:createtime', 0, -1),
|
|
||||||
]);
|
|
||||||
const notificationSettings = notificationTypes.map(type => ({
|
|
||||||
name: type,
|
|
||||||
label: `[[notifications:${type.replace(/_/g, '-')}]]`,
|
|
||||||
}));
|
|
||||||
res.render('admin/settings/user', {
|
|
||||||
title: '[[admin/menu:settings/user]]',
|
|
||||||
notificationSettings: notificationSettings,
|
|
||||||
groupsExemptFromNewUserRestrictions: groupData,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
settingsController.post = async (req, res) => {
|
|
||||||
const groupData = await groups.getNonPrivilegeGroups('groups:createtime', 0, -1);
|
|
||||||
res.render('admin/settings/post', {
|
|
||||||
title: '[[admin/menu:settings/post]]',
|
|
||||||
groupsExemptFromPostQueue: groupData,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
settingsController.advanced = async (req, res) => {
|
|
||||||
const groupData = await groups.getNonPrivilegeGroups('groups:createtime', 0, -1);
|
|
||||||
res.render('admin/settings/advanced', {
|
|
||||||
title: '[[admin/menu:settings/advanced]]',
|
|
||||||
groupsExemptFromMaintenanceMode: groupData,
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -108,6 +64,83 @@ settingsController.navigation = async function (req, res) {
|
|||||||
res.render('admin/settings/navigation', admin);
|
res.render('admin/settings/navigation', admin);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
settingsController.user = async (req, res) => {
|
||||||
|
const [notificationTypes, groupData] = await Promise.all([
|
||||||
|
notifications.getAllNotificationTypes(),
|
||||||
|
groups.getNonPrivilegeGroups('groups:createtime', 0, -1),
|
||||||
|
]);
|
||||||
|
const notificationSettings = notificationTypes.map(type => ({
|
||||||
|
name: type,
|
||||||
|
label: `[[notifications:${type.replace(/_/g, '-')}]]`,
|
||||||
|
}));
|
||||||
|
res.render('admin/settings/user', {
|
||||||
|
title: '[[admin/menu:settings/user]]',
|
||||||
|
notificationSettings: notificationSettings,
|
||||||
|
groupsExemptFromNewUserRestrictions: groupData,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
settingsController.reputation = async (req, res) => {
|
||||||
|
res.render(`admin/settings/reputation`, {
|
||||||
|
title: `[[admin/menu:settings/reputation]]`,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
settingsController.group = async (req, res) => {
|
||||||
|
res.render(`admin/settings/group`, {
|
||||||
|
title: `[[admin/menu:settings/group]]`,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
settingsController.tags = async (req, res) => {
|
||||||
|
res.render(`admin/settings/tags`, {
|
||||||
|
title: `[[admin/menu:settings/tags]]`,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
settingsController.post = async (req, res) => {
|
||||||
|
const groupData = await groups.getNonPrivilegeGroups('groups:createtime', 0, -1);
|
||||||
|
res.render('admin/settings/post', {
|
||||||
|
title: '[[admin/menu:settings/post]]',
|
||||||
|
groupsExemptFromPostQueue: groupData,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
settingsController.uploads = async (req, res) => {
|
||||||
|
res.render(`admin/settings/uploads`, {
|
||||||
|
title: `[[admin/menu:settings/uploads]]`,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
settingsController.email = async (req, res) => {
|
||||||
|
const emails = await emailer.getTemplates(meta.config);
|
||||||
|
|
||||||
|
res.render('admin/settings/email', {
|
||||||
|
title: '[[admin/menu:settings/email]]',
|
||||||
|
emails: emails,
|
||||||
|
sendable: emails.filter(e => !e.path.includes('_plaintext') && !e.path.includes('partials')).map(tpl => tpl.path),
|
||||||
|
services: emailer.listServices(),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
settingsController.chat = async (req, res) => {
|
||||||
|
res.render(`admin/settings/chat`, {
|
||||||
|
title: `[[admin/menu:settings/chat]]`,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
settingsController.pagination = async (req, res) => {
|
||||||
|
res.render(`admin/settings/pagination`, {
|
||||||
|
title: `[[admin/menu:settings/pagination]]`,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
settingsController.notifications = async (req, res) => {
|
||||||
|
res.render(`admin/settings/notifications`, {
|
||||||
|
title: `[[admin/menu:settings/notifications]]`,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
settingsController.api = async (req, res) => {
|
settingsController.api = async (req, res) => {
|
||||||
const page = parseInt(req.query.page, 10) || 1;
|
const page = parseInt(req.query.page, 10) || 1;
|
||||||
const resultsPerPage = 50;
|
const resultsPerPage = 50;
|
||||||
@@ -133,3 +166,27 @@ settingsController.activitypub = async (req, res) => {
|
|||||||
instanceCount,
|
instanceCount,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
settingsController.cookies = async (req, res) => {
|
||||||
|
res.render(`admin/settings/cookies`, {
|
||||||
|
title: `[[admin/menu:settings/cookies]]`,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
settingsController.webCrawler = async (req, res) => {
|
||||||
|
res.render(`admin/settings/web-crawler`, {
|
||||||
|
title: `[[admin/menu:settings/web-crawler]]`,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
settingsController.advanced = async (req, res) => {
|
||||||
|
const groupData = await groups.getNonPrivilegeGroups('groups:createtime', 0, -1);
|
||||||
|
res.render('admin/settings/advanced', {
|
||||||
|
title: '[[admin/menu:settings/advanced]]',
|
||||||
|
groupsExemptFromMaintenanceMode: groupData,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -20,12 +20,18 @@ themesController.get = async function (req, res, next) {
|
|||||||
themeConfig = JSON.parse(themeConfig);
|
themeConfig = JSON.parse(themeConfig);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err.code === 'ENOENT') {
|
if (err.code === 'ENOENT') {
|
||||||
return next(Error('invalid-data'));
|
return next(Error('[[error:invalid-data]]'));
|
||||||
}
|
}
|
||||||
return next(err);
|
return next(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
const screenshotPath = themeConfig.screenshot ? path.join(themeDir, themeConfig.screenshot) : defaultScreenshotPath;
|
const screenshotPath = themeConfig.screenshot ?
|
||||||
const exists = await file.exists(screenshotPath);
|
path.join(themeDir, themeConfig.screenshot) :
|
||||||
|
'';
|
||||||
|
|
||||||
|
if (screenshotPath && !screenshotPath.startsWith(themeDir)) {
|
||||||
|
throw new Error('[[error:invalid-path]]');
|
||||||
|
}
|
||||||
|
const exists = screenshotPath ? await file.exists(screenshotPath) : false;
|
||||||
res.sendFile(exists ? screenshotPath : defaultScreenshotPath);
|
res.sendFile(exists ? screenshotPath : defaultScreenshotPath);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ const privileges = require('../privileges');
|
|||||||
|
|
||||||
const groupsController = module.exports;
|
const groupsController = module.exports;
|
||||||
|
|
||||||
|
const url = nconf.get('url');
|
||||||
|
|
||||||
groupsController.list = async function (req, res) {
|
groupsController.list = async function (req, res) {
|
||||||
const sort = req.query.sort || 'alpha';
|
const sort = req.query.sort || 'alpha';
|
||||||
const page = parseInt(req.query.page, 10) || 1;
|
const page = parseInt(req.query.page, 10) || 1;
|
||||||
@@ -20,6 +22,13 @@ groupsController.list = async function (req, res) {
|
|||||||
getGroups(req, sort, page),
|
getGroups(req, sort, page),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
res.locals.linkTags = [
|
||||||
|
{
|
||||||
|
rel: 'canonical',
|
||||||
|
href: `${url}${req.url.replace(/^\/api/, '')}`,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
res.render('groups/list', {
|
res.render('groups/list', {
|
||||||
groups: groupData,
|
groups: groupData,
|
||||||
allowGroupCreation: allowGroupCreation,
|
allowGroupCreation: allowGroupCreation,
|
||||||
@@ -101,6 +110,13 @@ groupsController.details = async function (req, res, next) {
|
|||||||
return next();
|
return next();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
res.locals.linkTags = [
|
||||||
|
{
|
||||||
|
rel: 'canonical',
|
||||||
|
href: `${url}/groups/${lowercaseSlug}`,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
res.render('groups/details', {
|
res.render('groups/details', {
|
||||||
title: `[[pages:group, ${groupData.displayName}]]`,
|
title: `[[pages:group, ${groupData.displayName}]]`,
|
||||||
group: groupData,
|
group: groupData,
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ const helpers = require('./helpers');
|
|||||||
|
|
||||||
const tagsController = module.exports;
|
const tagsController = module.exports;
|
||||||
|
|
||||||
|
const url = nconf.get('url');
|
||||||
|
|
||||||
tagsController.getTag = async function (req, res) {
|
tagsController.getTag = async function (req, res) {
|
||||||
const tag = validator.escape(utils.cleanUpTag(req.params.tag, meta.config.maximumTagLength));
|
const tag = validator.escape(utils.cleanUpTag(req.params.tag, meta.config.maximumTagLength));
|
||||||
const page = parseInt(req.query.page, 10) || 1;
|
const page = parseInt(req.query.page, 10) || 1;
|
||||||
@@ -98,6 +100,13 @@ tagsController.getTags = async function (req, res) {
|
|||||||
topics.getCategoryTagsData(cids, 0, 99),
|
topics.getCategoryTagsData(cids, 0, 99),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
res.locals.linkTags = [
|
||||||
|
{
|
||||||
|
rel: 'canonical',
|
||||||
|
href: `${url}/tags`,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
res.render('tags', {
|
res.render('tags', {
|
||||||
tags: tags.filter(Boolean),
|
tags: tags.filter(Boolean),
|
||||||
displayTagSearch: canSearch,
|
displayTagSearch: canSearch,
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
const nconf = require('nconf');
|
||||||
|
|
||||||
const user = require('../user');
|
const user = require('../user');
|
||||||
const meta = require('../meta');
|
const meta = require('../meta');
|
||||||
|
|
||||||
@@ -12,6 +14,8 @@ const utils = require('../utils');
|
|||||||
|
|
||||||
const usersController = module.exports;
|
const usersController = module.exports;
|
||||||
|
|
||||||
|
const url = nconf.get('url');
|
||||||
|
|
||||||
usersController.index = async function (req, res, next) {
|
usersController.index = async function (req, res, next) {
|
||||||
const section = req.query.section || 'joindate';
|
const section = req.query.section || 'joindate';
|
||||||
const sectionToController = {
|
const sectionToController = {
|
||||||
@@ -206,6 +210,13 @@ async function render(req, res, data) {
|
|||||||
|
|
||||||
data['reputation:disabled'] = meta.config['reputation:disabled'];
|
data['reputation:disabled'] = meta.config['reputation:disabled'];
|
||||||
|
|
||||||
|
res.locals.linkTags = [
|
||||||
|
{
|
||||||
|
rel: 'canonical',
|
||||||
|
href: `${url}${req.url.replace(/^\/api/, '')}`,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
res.append('X-Total-Count', data.userCount);
|
res.append('X-Total-Count', data.userCount);
|
||||||
res.render('users', data);
|
res.render('users', data);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
module.exports = function (module) {
|
module.exports = function (module) {
|
||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
const helpers = require('./helpers');
|
const helpers = require('./helpers');
|
||||||
|
const { secureRandom } = require('../../utils');
|
||||||
|
|
||||||
module.setAdd = async function (key, value) {
|
module.setAdd = async function (key, value) {
|
||||||
if (!Array.isArray(value)) {
|
if (!Array.isArray(value)) {
|
||||||
@@ -200,7 +201,7 @@ module.exports = function (module) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const randomIndex = Math.floor(Math.random() * data.members.length);
|
const randomIndex = secureRandom(0, data.members.length - 1);
|
||||||
const value = data.members[randomIndex];
|
const value = data.members[randomIndex];
|
||||||
await module.setRemove(data._key, value);
|
await module.setRemove(data._key, value);
|
||||||
return value;
|
return value;
|
||||||
|
|||||||
@@ -687,6 +687,7 @@ Flags.canFlag = async function (type, id, uid, skipLimitCheck = false) {
|
|||||||
Flags.getTarget = async function (type, id, uid) {
|
Flags.getTarget = async function (type, id, uid) {
|
||||||
if (type === 'user') {
|
if (type === 'user') {
|
||||||
const userData = await user.getUserData(id);
|
const userData = await user.getUserData(id);
|
||||||
|
userData.aboutme = validator.escape(String(userData.aboutme));
|
||||||
return userData && userData.uid ? userData : {};
|
return userData && userData.uid ? userData : {};
|
||||||
}
|
}
|
||||||
if (type === 'post') {
|
if (type === 'post') {
|
||||||
|
|||||||
@@ -370,26 +370,21 @@ async function createAdmin() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function retryPassword(originalResults) {
|
async function retryPassword(originalResults) {
|
||||||
// Ask only the password questions
|
|
||||||
const results = await prompt.get(passwordQuestions);
|
const results = await prompt.get(passwordQuestions);
|
||||||
|
|
||||||
// Update the original data with newly collected password
|
|
||||||
originalResults.password = results.password;
|
originalResults.password = results.password;
|
||||||
originalResults['password:confirm'] = results['password:confirm'];
|
originalResults['password:confirm'] = results['password:confirm'];
|
||||||
|
|
||||||
// Send back to success to handle
|
|
||||||
return await success(originalResults);
|
return await success(originalResults);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add the password questions
|
|
||||||
questions = questions.concat(passwordQuestions);
|
questions = questions.concat(passwordQuestions);
|
||||||
|
|
||||||
if (!install.values) {
|
if (!install.values) {
|
||||||
const results = await prompt.get(questions);
|
const results = await prompt.get(questions);
|
||||||
return await success(results);
|
return await success(results);
|
||||||
}
|
}
|
||||||
// If automated setup did not provide a user password, generate one,
|
|
||||||
// it will be shown to the user upon setup completion
|
|
||||||
if (!install.values.hasOwnProperty('admin:password') && !nconf.get('admin:password')) {
|
if (!install.values.hasOwnProperty('admin:password') && !nconf.get('admin:password')) {
|
||||||
console.log('Password was not provided during automated setup, generating one...');
|
console.log('Password was not provided during automated setup, generating one...');
|
||||||
password = utils.generateUUID().slice(0, 8);
|
password = utils.generateUUID().slice(0, 8);
|
||||||
|
|||||||
@@ -11,7 +11,11 @@ let cached;
|
|||||||
|
|
||||||
// cache buster is an 11-character, lowercase, alphanumeric string
|
// cache buster is an 11-character, lowercase, alphanumeric string
|
||||||
function generate() {
|
function generate() {
|
||||||
return (Math.random() * 1e18).toString(32).slice(0, 11);
|
const crypto = require('crypto');
|
||||||
|
const length = 11;
|
||||||
|
const generated = crypto.randomBytes(Math.ceil(length / 2))
|
||||||
|
.toString('hex').slice(0, length);
|
||||||
|
return generated;
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.write = async function write() {
|
exports.write = async function write() {
|
||||||
|
|||||||
@@ -47,7 +47,6 @@ middleware.checkPrivileges = helpers.try(async (req, res, next) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If user does not have password
|
|
||||||
const hasPassword = await user.hasPassword(req.uid);
|
const hasPassword = await user.hasPassword(req.uid);
|
||||||
if (!hasPassword) {
|
if (!hasPassword) {
|
||||||
return next();
|
return next();
|
||||||
|
|||||||
@@ -29,7 +29,9 @@ async function getFakeHash() {
|
|||||||
if (fakeHashCache) {
|
if (fakeHashCache) {
|
||||||
return fakeHashCache;
|
return fakeHashCache;
|
||||||
}
|
}
|
||||||
fakeHashCache = await exports.hash(12, Math.random().toString());
|
const length = 18;
|
||||||
|
fakeHashCache = crypto.randomBytes(Math.ceil(length / 2))
|
||||||
|
.toString('hex').slice(0, length);
|
||||||
return fakeHashCache;
|
return fakeHashCache;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -33,16 +33,28 @@ module.exports = function (app, name, middleware, controllers) {
|
|||||||
helpers.setupAdminPageRoute(app, `/${name}/manage/uploads`, middlewares, controllers.admin.uploads.get);
|
helpers.setupAdminPageRoute(app, `/${name}/manage/uploads`, middlewares, controllers.admin.uploads.get);
|
||||||
helpers.setupAdminPageRoute(app, `/${name}/manage/digest`, middlewares, controllers.admin.digest.get);
|
helpers.setupAdminPageRoute(app, `/${name}/manage/digest`, middlewares, controllers.admin.digest.get);
|
||||||
|
|
||||||
helpers.setupAdminPageRoute(app, `/${name}/settings/email`, middlewares, controllers.admin.settings.email);
|
helpers.setupAdminPageRoute(app, `/${name}/settings/general`, middlewares, controllers.admin.settings.general);
|
||||||
helpers.setupAdminPageRoute(app, `/${name}/settings/user`, middlewares, controllers.admin.settings.user);
|
|
||||||
helpers.setupAdminPageRoute(app, `/${name}/settings/post`, middlewares, controllers.admin.settings.post);
|
|
||||||
helpers.setupAdminPageRoute(app, `/${name}/settings/advanced`, middlewares, controllers.admin.settings.advanced);
|
|
||||||
helpers.setupAdminPageRoute(app, `/${name}/settings/navigation`, middlewares, controllers.admin.settings.navigation);
|
helpers.setupAdminPageRoute(app, `/${name}/settings/navigation`, middlewares, controllers.admin.settings.navigation);
|
||||||
|
helpers.setupAdminPageRoute(app, `/${name}/settings/user`, middlewares, controllers.admin.settings.user);
|
||||||
|
helpers.setupAdminPageRoute(app, `/${name}/settings/reputation`, middlewares, controllers.admin.settings.reputation);
|
||||||
|
helpers.setupAdminPageRoute(app, `/${name}/settings/group`, middlewares, controllers.admin.settings.group);
|
||||||
|
helpers.setupAdminPageRoute(app, `/${name}/settings/tags`, middlewares, controllers.admin.settings.tags);
|
||||||
|
helpers.setupAdminPageRoute(app, `/${name}/settings/post`, middlewares, controllers.admin.settings.post);
|
||||||
|
helpers.setupAdminPageRoute(app, `/${name}/settings/uploads`, middlewares, controllers.admin.settings.uploads);
|
||||||
|
helpers.setupAdminPageRoute(app, `/${name}/settings/email`, middlewares, controllers.admin.settings.email);
|
||||||
|
helpers.setupAdminPageRoute(app, `/${name}/settings/chat`, middlewares, controllers.admin.settings.chat);
|
||||||
|
helpers.setupAdminPageRoute(app, `/${name}/settings/pagination`, middlewares, controllers.admin.settings.pagination);
|
||||||
|
helpers.setupAdminPageRoute(app, `/${name}/settings/notifications`, middlewares, controllers.admin.settings.notifications);
|
||||||
helpers.setupAdminPageRoute(app, `/${name}/settings/api`, middlewares, controllers.admin.settings.api);
|
helpers.setupAdminPageRoute(app, `/${name}/settings/api`, middlewares, controllers.admin.settings.api);
|
||||||
helpers.setupAdminPageRoute(app, `/${name}/settings/activitypub`, middlewares, controllers.admin.settings.activitypub);
|
helpers.setupAdminPageRoute(app, `/${name}/settings/activitypub`, middlewares, controllers.admin.settings.activitypub);
|
||||||
helpers.setupAdminPageRoute(app, `/${name}/settings/:term?`, middlewares, controllers.admin.settings.get);
|
helpers.setupAdminPageRoute(app, `/${name}/settings/cookies`, middlewares, controllers.admin.settings.cookies);
|
||||||
|
helpers.setupAdminPageRoute(app, `/${name}/settings/web-crawler`, middlewares, controllers.admin.settings.webCrawler);
|
||||||
|
helpers.setupAdminPageRoute(app, `/${name}/settings/advanced`, middlewares, controllers.admin.settings.advanced);
|
||||||
|
|
||||||
|
helpers.setupAdminPageRoute(app, `/${name}/appearance/themes`, middlewares, controllers.admin.appearance.themes);
|
||||||
|
helpers.setupAdminPageRoute(app, `/${name}/appearance/skins`, middlewares, controllers.admin.appearance.skins);
|
||||||
|
helpers.setupAdminPageRoute(app, `/${name}/appearance/customise`, middlewares, controllers.admin.appearance.customise);
|
||||||
|
|
||||||
helpers.setupAdminPageRoute(app, `/${name}/appearance/:term?`, middlewares, controllers.admin.appearance.get);
|
|
||||||
|
|
||||||
helpers.setupAdminPageRoute(app, `/${name}/extend/plugins`, middlewares, controllers.admin.plugins.get);
|
helpers.setupAdminPageRoute(app, `/${name}/extend/plugins`, middlewares, controllers.admin.plugins.get);
|
||||||
helpers.setupAdminPageRoute(app, `/${name}/extend/widgets`, middlewares, controllers.admin.extend.widgets.get);
|
helpers.setupAdminPageRoute(app, `/${name}/extend/widgets`, middlewares, controllers.admin.extend.widgets.get);
|
||||||
|
|||||||
@@ -24,7 +24,6 @@ require('./user/status')(SocketUser);
|
|||||||
require('./user/picture')(SocketUser);
|
require('./user/picture')(SocketUser);
|
||||||
require('./user/registration')(SocketUser);
|
require('./user/registration')(SocketUser);
|
||||||
|
|
||||||
// Password Reset
|
|
||||||
SocketUser.reset = {};
|
SocketUser.reset = {};
|
||||||
|
|
||||||
SocketUser.reset.send = async function (socket, email) {
|
SocketUser.reset.send = async function (socket, email) {
|
||||||
@@ -47,10 +46,10 @@ SocketUser.reset.send = async function (socket, email) {
|
|||||||
try {
|
try {
|
||||||
await user.reset.send(email);
|
await user.reset.send(email);
|
||||||
await logEvent('[[success:success]]');
|
await logEvent('[[success:success]]');
|
||||||
await sleep(2500 + ((Math.random() * 500) - 250));
|
await sleep(2500 + (utils.secureRandom(0, 500) - 250));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
await logEvent(err.message);
|
await logEvent(err.message);
|
||||||
await sleep(2500 + ((Math.random() * 500) - 250));
|
await sleep(2500 + (utils.secureRandom(0, 500) - 250));
|
||||||
const internalErrors = ['[[error:invalid-email]]'];
|
const internalErrors = ['[[error:invalid-email]]'];
|
||||||
if (!internalErrors.includes(err.message)) {
|
if (!internalErrors.includes(err.message)) {
|
||||||
throw err;
|
throw err;
|
||||||
|
|||||||
@@ -93,7 +93,6 @@ module.exports = function (User) {
|
|||||||
if (!fields.length) {
|
if (!fields.length) {
|
||||||
fields = results.whitelist;
|
fields = results.whitelist;
|
||||||
} else {
|
} else {
|
||||||
// Never allow password retrieval via this method
|
|
||||||
fields = fields.filter(value => value !== 'password');
|
fields = fields.filter(value => value !== 'password');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -282,7 +282,7 @@ User.addInterstitials = function (callback) {
|
|||||||
plugins.hooks.register('core', {
|
plugins.hooks.register('core', {
|
||||||
hook: 'filter:register.interstitial',
|
hook: 'filter:register.interstitial',
|
||||||
method: [
|
method: [
|
||||||
User.interstitials.email, // Email address (for password reset + digest)
|
User.interstitials.email, // Email address
|
||||||
User.interstitials.gdpr, // GDPR information collection/processing consent + email consent
|
User.interstitials.gdpr, // GDPR information collection/processing consent + email consent
|
||||||
User.interstitials.tou, // Forum Terms of Use
|
User.interstitials.tou, // Forum Terms of Use
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -108,7 +108,6 @@ UserReset.commit = async function (code, password) {
|
|||||||
'password:shaWrapped': 1,
|
'password:shaWrapped': 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
// don't verify email if password reset is due to expiry
|
|
||||||
const isPasswordExpired = userData.passwordExpiry && userData.passwordExpiry < Date.now();
|
const isPasswordExpired = userData.passwordExpiry && userData.passwordExpiry < Date.now();
|
||||||
if (!isPasswordExpired) {
|
if (!isPasswordExpired) {
|
||||||
data['email:confirmed'] = 1;
|
data['email:confirmed'] = 1;
|
||||||
|
|||||||
10
src/utils.js
10
src/utils.js
@@ -31,6 +31,16 @@ utils.generateUUID = function () {
|
|||||||
return rnd.join('-');
|
return rnd.join('-');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
utils.secureRandom = function (low, high) {
|
||||||
|
if (low > high) {
|
||||||
|
throw new Error("The 'low' parameter must be less than or equal to the 'high' parameter.");
|
||||||
|
}
|
||||||
|
const randomBuffer = crypto.randomBytes(4);
|
||||||
|
const randomInt = randomBuffer.readUInt32BE(0);
|
||||||
|
const range = high - low + 1;
|
||||||
|
return low + (randomInt % range);
|
||||||
|
};
|
||||||
|
|
||||||
utils.getSass = function () {
|
utils.getSass = function () {
|
||||||
try {
|
try {
|
||||||
const sass = require('sass-embedded');
|
const sass = require('sass-embedded');
|
||||||
|
|||||||
@@ -103,7 +103,7 @@
|
|||||||
<div id="smtp-transport" class="mb-4">
|
<div id="smtp-transport" class="mb-4">
|
||||||
<h5 class="fw-bold tracking-tight settings-header">[[admin/settings/email:smtp-transport]]</h5>
|
<h5 class="fw-bold tracking-tight settings-header">[[admin/settings/email:smtp-transport]]</h5>
|
||||||
|
|
||||||
<div class="alert alert-warning">
|
<div class="alert alert-warning text-sm p-2">
|
||||||
[[admin/settings/email:smtp-transport-help]]
|
[[admin/settings/email:smtp-transport-help]]
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -293,7 +293,7 @@
|
|||||||
<hr/>
|
<hr/>
|
||||||
|
|
||||||
<div id="post-sharing" class="mb-4">
|
<div id="post-sharing" class="mb-4">
|
||||||
<h5 class="fw-bold tracking-tight settings-header">[[admin/settings/social:post-sharing]]</h5>
|
<h5 class="fw-bold tracking-tight settings-header">[[admin/settings/general:post-sharing]]</h5>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<div class="form-group" id="postSharingNetworks">
|
<div class="form-group" id="postSharingNetworks">
|
||||||
{{{ each postSharing }}}
|
{{{ each postSharing }}}
|
||||||
@@ -304,7 +304,7 @@
|
|||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
{{{ end }}}
|
{{{ end }}}
|
||||||
<p class="form-text">[[admin/settings/social:info-plugins-additional]]</p>
|
<p class="form-text">[[admin/settings/general:info-plugins-additional]]</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -123,7 +123,7 @@
|
|||||||
{{{ end }}}
|
{{{ end }}}
|
||||||
|
|
||||||
{{{ if type_bool.user }}}
|
{{{ if type_bool.user }}}
|
||||||
<div class="d-flex gap-2">
|
<div class="d-flex gap-2 align-items-center lh-1 mb-2">
|
||||||
<a href="{config.relative_path}/user/{./target.userslug}">{buildAvatar(target, "16px", true)}</a>
|
<a href="{config.relative_path}/user/{./target.userslug}">{buildAvatar(target, "16px", true)}</a>
|
||||||
<a href="{config.relative_path}/user/{./target.userslug}">{target.username}</a>
|
<a href="{config.relative_path}/user/{./target.userslug}">{target.username}</a>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -35,6 +35,8 @@
|
|||||||
You are just a few steps away from launching your own NodeBB forum!
|
You are just a few steps away from launching your own NodeBB forum!
|
||||||
</p>
|
</p>
|
||||||
<form id="install" action="/" method="post" autocomplete="off">
|
<form id="install" action="/" method="post" autocomplete="off">
|
||||||
|
<input type="hidden" name="csrf_token" value="{csrf_token}" />
|
||||||
|
|
||||||
{{{ if !skipGeneralSetup }}}
|
{{{ if !skipGeneralSetup }}}
|
||||||
<div class="general">
|
<div class="general">
|
||||||
<p>
|
<p>
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="flex-grow-1 ms-3">
|
<div class="flex-grow-1 ms-3">
|
||||||
<p>
|
<p>
|
||||||
<code>{./name}</code>
|
<code style="word-break: break-all;">{./name}</code>
|
||||||
</p>
|
</p>
|
||||||
<button class="btn btn-danger btn-sm text-nowrap" data-action="remove"><i class="fa fa-times"></i> [[modules:thumbs.modal.remove]]</button>
|
<button class="btn btn-danger btn-sm text-nowrap" data-action="remove"><i class="fa fa-times"></i> [[modules:thumbs.modal.remove]]</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -138,7 +138,7 @@ describe('Admin Controllers', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should load general settings page', async () => {
|
it('should load general settings page', async () => {
|
||||||
const { response, body } = await request.get(`${nconf.get('url')}/admin/settings`, { jar: jar });
|
const { response, body } = await request.get(`${nconf.get('url')}/admin/settings/general`, { jar: jar });
|
||||||
assert.equal(response.statusCode, 200);
|
assert.equal(response.statusCode, 200);
|
||||||
assert(body);
|
assert(body);
|
||||||
});
|
});
|
||||||
@@ -423,23 +423,6 @@ describe('Admin Controllers', () => {
|
|||||||
assert(body);
|
assert(body);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should load /admin/settings/languages', async () => {
|
|
||||||
const { response, body } = await request.get(`${nconf.get('url')}/api/admin/settings/languages`, { jar });
|
|
||||||
assert.equal(response.statusCode, 200);
|
|
||||||
assert(body);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should load /admin/settings/social', async () => {
|
|
||||||
const { body } = await request.get(`${nconf.get('url')}/api/admin/settings/general`, { jar });
|
|
||||||
assert(body);
|
|
||||||
const sharing = body.postSharing.map(network => network && network.id);
|
|
||||||
assert(sharing.includes('facebook'));
|
|
||||||
assert(sharing.includes('twitter'));
|
|
||||||
assert(sharing.includes('linkedin'));
|
|
||||||
assert(sharing.includes('whatsapp'));
|
|
||||||
assert(sharing.includes('telegram'));
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should load /admin/manage/tags', async () => {
|
it('should load /admin/manage/tags', async () => {
|
||||||
const { response, body } = await request.get(`${nconf.get('url')}/api/admin/manage/tags`, { jar });
|
const { response, body } = await request.get(`${nconf.get('url')}/api/admin/manage/tags`, { jar });
|
||||||
assert.equal(response.statusCode, 200);
|
assert.equal(response.statusCode, 200);
|
||||||
@@ -476,6 +459,12 @@ describe('Admin Controllers', () => {
|
|||||||
assert(body);
|
assert(body);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should load /admin/appearance/skins', async () => {
|
||||||
|
const { response, body } = await request.get(`${nconf.get('url')}/api/admin/appearance/skins`, { jar });
|
||||||
|
assert.equal(response.statusCode, 200);
|
||||||
|
assert(body);
|
||||||
|
});
|
||||||
|
|
||||||
it('should load /admin/appearance/customise', async () => {
|
it('should load /admin/appearance/customise', async () => {
|
||||||
const { response, body } = await request.get(`${nconf.get('url')}/api/admin/appearance/customise`, { jar });
|
const { response, body } = await request.get(`${nconf.get('url')}/api/admin/appearance/customise`, { jar });
|
||||||
assert.equal(response.statusCode, 200);
|
assert.equal(response.statusCode, 200);
|
||||||
|
|||||||
@@ -102,7 +102,7 @@ describe('Utility Methods', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('UUID generation', () => {
|
describe('UUID generation / secureRandom', () => {
|
||||||
it('return unique random value every time', () => {
|
it('return unique random value every time', () => {
|
||||||
delete require.cache[require.resolve('../src/utils')];
|
delete require.cache[require.resolve('../src/utils')];
|
||||||
const { generateUUID } = require('../src/utils');
|
const { generateUUID } = require('../src/utils');
|
||||||
@@ -110,6 +110,19 @@ describe('Utility Methods', () => {
|
|||||||
const uuid2 = generateUUID();
|
const uuid2 = generateUUID();
|
||||||
assert.notEqual(uuid1, uuid2, 'matches');
|
assert.notEqual(uuid1, uuid2, 'matches');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should return a random number between 1-10 inclusive', () => {
|
||||||
|
const { secureRandom } = require('../src/utils');
|
||||||
|
const r1 = secureRandom(1, 10);
|
||||||
|
assert(r1 >= 1);
|
||||||
|
assert(r1 <= 10);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should always return 3', () => {
|
||||||
|
const { secureRandom } = require('../src/utils');
|
||||||
|
const r1 = secureRandom(3, 3);
|
||||||
|
assert.strictEqual(r1, 3);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('cleanUpTag', () => {
|
describe('cleanUpTag', () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user