Compare commits

..

116 Commits

Author SHA1 Message Date
Barış Soner Uşaklı
8228eeb468 chore: up version 2025-05-12 11:07:15 -04:00
Barış Soner Uşaklı
fc09f6c4f9 fix: escape flag filters 2025-05-12 11:00:38 -04:00
Barış Soner Uşaklı
7a26672872 fix: sql injection in sortedSetScan 2025-05-12 10:59:53 -04:00
Barış Soner Uşaklı
de820ae198 chore: up persona 2024-06-24 16:04:26 -04:00
Barış Soner Uşaklı
961a193787 chore: up persona 2024-06-24 16:02:28 -04:00
Barış Soner Uşaklı
4ab31e3f79 chore: openapi 2024-06-24 15:54:42 -04:00
Barış Soner Uşaklı
338f89deb5 backport author, and structured data fix to 2.x 2024-06-24 15:53:42 -04:00
Barış Soner Uşaklı
dc14d6a8d1 chore: up version 2023-08-22 12:49:06 -04:00
Barış Soner Uşaklı
fb43f9ae10 chore: update composer-default 2023-08-22 12:07:02 -04:00
Barış Soner Uşaklı
638e098f30 fix: #11756, fix unique visitor stats in acp table 2023-06-26 09:56:58 -04:00
Julian Lam
2514aace4e chore: bump nodebb version 2023-06-13 14:54:56 -04:00
Julian Lam
9ec7ab4afc fix: improper neutralization of user input in image wrapping code
(Backport of: 1d1639d46f)
2023-06-13 14:53:27 -04:00
Barış Soner Uşaklı
dd5ed9e507 fix: closes #11617, upgrade csrf-sync dep
adds back req.csrfToken()
2023-05-20 21:05:02 -04:00
Barış Soner Uşaklı
8bc8cf1ba0 lint 2023-05-15 12:15:48 -04:00
Barış Soner Uşaklı
62e162cf1e fix: backport ws token fix 2023-05-15 11:55:18 -04:00
psibean
a5d92da9dd Replace csurf with csrf-sync 2023-05-15 11:48:25 -04:00
Julian Lam
2bd6eea2fa fix: #11554, email requirement bypass by sending in whitespace 2023-05-02 12:01:28 -04:00
Misty Release Bot
42b9fbc91c chore: incrementing version number - v2.8.12
(cherry picked from commit 3e494a1ea0)
Signed-off-by: Misty Release Bot <deploy@nodebb.org>
2023-04-26 14:34:45 +00:00
Misty Release Bot
5c0bf7ccbe Merge commit '30b6bcfca117e667c262c0462fc5f0100e6a436c' into v2.x 2023-04-26 14:34:42 +00:00
Barış Soner Uşaklı
30b6bcfca1 fix: #11519, clear parent cache all the way to root 2023-04-26 10:13:27 -04:00
Barış Soner Uşaklı
de2669a2c6 fix: only remove deleted tag, closes #11515 2023-04-24 15:39:00 -04:00
Barış Soner Uşaklı
21fb8590e5 test: remove old comment 2023-04-23 18:59:18 -04:00
Veronikya
c931183287 fix: NodeBB#11482 thumbs Post Can not upload a thumbnail, only multip… (#11483)
* fix: NodeBB#11482 thumbs Post Can not upload a thumbnail, only multiple uploads

* Modify upload thumbnail test

* Modify upload thumbnail test +,

* Get rid of v2 uploads test

* edit times

* Modify amount of files associated post test

* edit post file amount
2023-04-15 17:56:36 -04:00
Brutus5000
ae5afdbc66 feat: name theme on error:theme-not-set-in-configuration 2023-04-11 13:42:21 -04:00
Julian Lam
5343d2a01b chore: removing superfluous changelog items 2023-04-10 21:51:12 -04:00
Misty Release Bot
2ec81eff43 chore: incrementing version number - v2.8.11
(cherry picked from commit 82f0efb14b)
Signed-off-by: Misty Release Bot <deploy@nodebb.org>
2023-04-11 01:49:11 +00:00
Misty Release Bot
f2ca93f6c6 chore: update changelog for v2.8.11 2023-04-11 01:49:11 +00:00
Misty Release Bot
82f0efb14b chore: incrementing version number - v2.8.11 2023-04-11 01:49:11 +00:00
Misty Release Bot
df08b47163 Merge commit 'c27567289f9937abd4abe6960a9b6e387cf68331' into v2.x 2023-04-11 01:49:09 +00:00
Opliko
c27567289f ci: publish to ghcr instead of docker hub 2023-04-05 14:38:47 -04:00
Julian Lam
c33730530e Revert "docs: update readme with new screenshot and updated copy for Harmony"
This reverts commit 67055006df.
2023-03-29 10:32:05 -04:00
Julian Lam
67055006df docs: update readme with new screenshot and updated copy for Harmony 2023-03-29 10:31:39 -04:00
Barış Soner Uşaklı
e0b2065802 test: update socket.io test 2023-03-28 08:15:42 -04:00
Barış Soner Uşaklı
4d2d76897a fix: don't crash on objects with toString property 2023-03-28 08:08:59 -04:00
Barış Soner Uşaklı
7397873db3 fix: fire action:user.online on user login 2023-03-27 22:16:41 -04:00
Misty Release Bot
5b7c3671c8 chore: incrementing version number - v2.8.10
(cherry picked from commit 48c1c7594d)
Signed-off-by: Misty Release Bot <deploy@nodebb.org>
2023-03-27 18:10:57 +00:00
Misty Release Bot
188ec62f9a chore: update changelog for v2.8.10 2023-03-27 18:10:57 +00:00
Misty Release Bot
48c1c7594d chore: incrementing version number - v2.8.10 2023-03-27 18:10:57 +00:00
Misty Release Bot
73ff25887c Merge commit '830f142b7aea2e597294a84d52c05aab3a3539ca' into v2.x 2023-03-27 15:12:54 +00:00
Julian Lam
830f142b7a fix: #11403, remove loader.js crash counter logic 2023-03-27 10:55:44 -04:00
Barış Soner Uşaklı
1aff9cad91 lint: fix arrow 2023-03-27 10:47:15 -04:00
Barış Soner Uşaklı
37b48b82a4 fix: don't crash if event name is not a string 2023-03-27 10:38:53 -04:00
Barış Soner Uşaklı
e9a8e19508 chore: up composer-default 2023-03-21 10:13:21 -04:00
Barış Soner Uşaklı
894f392bfc lint: whitespace 2023-03-20 11:17:05 -04:00
Barış Soner Uşaklı
c2961ad4cd fix: closes #11173, move cache clear code
if 2 deps were updated only one of them was cleared from require.cache. ie commander & lru-cache both has major version bump then only commander would be cleared from cache since it throws first
2023-03-20 11:05:48 -04:00
Misty Release Bot
57f14e419f chore: incrementing version number - v2.8.9
(cherry picked from commit fb100ac731)
Signed-off-by: Misty Release Bot <deploy@nodebb.org>
2023-03-19 16:31:26 +00:00
Misty Release Bot
18b2150edd chore: update changelog for v2.8.9 2023-03-19 16:31:26 +00:00
Misty Release Bot
fb100ac731 chore: incrementing version number - v2.8.9 2023-03-19 16:31:26 +00:00
Misty Release Bot
bb725987b3 Merge commit '73a50d17180dcd6cb42ef9cf305a480f92b4af05' into v2.x 2023-03-19 16:31:24 +00:00
Barış Soner Uşaklı
73a50d1718 chore: up cron 2023-03-19 12:21:06 -04:00
Julian Lam
93aa43f717 style: more fixes 2023-03-17 15:48:44 -04:00
Phạm Tấn Minh Tiến
9ed6961af8 fix lint 2023-03-17 15:48:44 -04:00
Phạm Tấn Minh Tiến
4b94c033c4 wrap quotes to prevent stripping leading 0 2023-03-17 15:48:44 -04:00
Barış Soner Uşaklı
9e685e657a test: openapi for thumbs 2023-03-15 15:18:05 -04:00
Barış Soner Uşaklı
767c1d1faf fix: thumb remove on windows, closes #11357 2023-03-14 15:09:12 -04:00
Barış Soner Uşaklı
a3a38e4ba3 fix: #11357 clear cache on thumb remove 2023-03-14 14:30:46 -04:00
Barış Soner Uşaklı
cfd5027245 fix: closes #11352, try/catch rss feeds 2023-03-11 16:07:02 -05:00
Barış Soner Uşaklı
56427e4f9d fix: closes #11343, don't crash if tags array is empty 2023-03-10 11:40:02 -05:00
Misty Release Bot
b331b9423b chore: incrementing version number - v2.8.8
(cherry picked from commit f5a59991fc)
Signed-off-by: Misty Release Bot <deploy@nodebb.org>
2023-03-09 14:58:29 +00:00
Misty Release Bot
c03d5db71e chore: update changelog for v2.8.8 2023-03-09 14:58:28 +00:00
Misty Release Bot
e45a6de24b Merge commit '22fc8fe38fd3b3c8ba6300ca6d12d90eb9b990ca' into v2.x 2023-03-09 14:58:22 +00:00
Misty Release Bot
3f8248d673 chore: incrementing version number - v2.8.7
(cherry picked from commit 6976925943)
Signed-off-by: Misty Release Bot <deploy@nodebb.org>
2023-03-01 15:51:30 +00:00
Misty Release Bot
f4282c091b Merge commit '791551098cb4a56edbae824e45b6f0a10138695b' into v2.x 2023-03-01 15:51:22 +00:00
Misty Release Bot
af6ce44737 chore: incrementing version number - v2.8.6
(cherry picked from commit 76732140f3)
Signed-off-by: Misty Release Bot <deploy@nodebb.org>
2023-02-03 16:39:40 +00:00
Misty Release Bot
c6681a1725 Merge commit 'bf92ee0e5fcd0b7a69bb58ec4baaf3b6225ebd6b' into v2.x 2023-02-03 16:39:38 +00:00
Misty Release Bot
bff5ce2d79 chore: incrementing version number - v2.8.5
(cherry picked from commit 93ccf604db)
Signed-off-by: Misty Release Bot <deploy@nodebb.org>
2023-01-27 14:35:25 +00:00
Misty Release Bot
4821b21e81 Merge commit 'f6c96948fe7cee13575ab9c93af6fe7fb9d7b722' into v2.x 2023-01-27 14:35:21 +00:00
Misty Release Bot
a46b2bbc45 chore: incrementing version number - v2.8.4
(cherry picked from commit b9553613ab)
Signed-off-by: Misty Release Bot <deploy@nodebb.org>
2023-01-26 14:38:07 +00:00
Misty Release Bot
ce924eca0d Merge commit 'c3653bee60740e410bf28808e29ffed6ab373bf9' into v2.x 2023-01-26 14:38:03 +00:00
Misty Release Bot
c20b20a7aa chore: incrementing version number - v2.8.3
(cherry picked from commit 4c46ff42f6)
Signed-off-by: Misty Release Bot <deploy@nodebb.org>
2023-01-25 19:37:34 +00:00
Misty Release Bot
82eb55d77d Merge commit '89e059a0841f4265d16b28a99ebf847dd10fa055' into v2.x 2023-01-25 19:37:31 +00:00
Misty Release Bot
050e43f8b4 chore: incrementing version number - v2.8.2
(cherry picked from commit 1d5eff2365)
Signed-off-by: Misty Release Bot <deploy@nodebb.org>
2023-01-13 18:38:13 +00:00
Misty Release Bot
9b6dad367d Merge commit '25ae58e8a057d9c640fbb50f675eadcdbe442aa9' into v2.x 2023-01-13 18:38:09 +00:00
Misty Release Bot
727f879e5b chore: incrementing version number - v2.8.1
(cherry picked from commit 96bdbf52b8)
Signed-off-by: Misty Release Bot <deploy@nodebb.org>
2022-12-30 20:48:48 +00:00
Misty Release Bot
fe662f3a46 Merge commit '8a69e740a859cf2eb4a12a0167c1ac76a48c33db' into v2.x 2022-12-30 20:48:22 +00:00
Misty Release Bot
8e77673d39 chore: incrementing version number - v2.8.0
(cherry picked from commit 7ce758d698)
Signed-off-by: Misty Release Bot <deploy@nodebb.org>
2022-12-21 22:10:49 +00:00
Misty Release Bot
3f950d5162 Merge commit 'ef500af8e6c618d86069cbf0be0d21e8c3f6e527' into v2.x 2022-12-21 22:10:47 +00:00
Misty Release Bot
96cc0617c5 chore: incrementing version number - v2.7.0
(cherry picked from commit 098097257d)
Signed-off-by: Misty Release Bot <deploy@nodebb.org>
2022-12-14 19:36:38 +00:00
Misty Release Bot
ccf8739344 Merge commit '9ee8502d7a8ba41ce6ded74b1ce1fbbe180b1dda' into v2.x 2022-12-14 19:36:36 +00:00
Misty Release Bot
7e52a7a574 chore: incrementing version number - v2.6.1
(cherry picked from commit f8e947e2a7)
Signed-off-by: Misty Release Bot <deploy@nodebb.org>
2022-11-28 01:01:10 +00:00
Misty Release Bot
21d9806ca9 Merge commit '48d143921753914da45926cca6370a92ed0c46b8' into v2.x 2022-11-28 01:00:52 +00:00
Misty Release Bot
e7fcf482f3 chore: incrementing version number - v2.6.0
(cherry picked from commit 12f0541dfa)
Signed-off-by: Misty Release Bot <deploy@nodebb.org>
2022-11-23 19:04:45 +00:00
Misty Release Bot
d80c80b618 Merge commit 'c7aa4ebf47f7b87db1f5efa0c9662b21cff7b194' into v2.x 2022-11-23 19:04:37 +00:00
Misty Release Bot
dec0e7deac chore: incrementing version number - v2.5.8
(cherry picked from commit 466263172a)
Signed-off-by: Misty Release Bot <deploy@nodebb.org>
2022-11-09 18:46:09 +00:00
Misty Release Bot
c7ff98a12d Merge commit '2f9d8c350e54543f608d3d4c8e1a49bbb6cdea38' into v2.x 2022-11-09 18:42:47 +00:00
Misty Release Bot
5836bf4a05 chore: incrementing version number - v2.5.7
(cherry picked from commit dd6d104820)
Signed-off-by: Misty Release Bot <deploy@nodebb.org>
2022-10-14 15:59:58 +00:00
Misty Release Bot
a5357812c6 Merge commit 'dc4a850cacecb8c57923803363dac9bb61221bba' into v2.x 2022-10-14 15:59:56 +00:00
Misty Release Bot
c7bd7dbfe6 chore: incrementing version number - v2.5.6
(cherry picked from commit 7dc45afa4c)
Signed-off-by: Misty Release Bot <deploy@nodebb.org>
2022-10-13 14:21:02 +00:00
Misty Release Bot
ec4dadabd4 Merge commit '67efaeb4b8e03417dfc3b575f19249f18f4cb3d6' into v2.x 2022-10-13 14:21:00 +00:00
Misty Release Bot
3509ed9461 chore: incrementing version number - v2.5.5
(cherry picked from commit 58b2f10ee9)
Signed-off-by: Misty Release Bot <deploy@nodebb.org>
2022-10-11 17:07:16 +00:00
Misty Release Bot
cb8d94563a Merge commit 'b91ef6dd761d643383d1eb4f4ac3abd5e55c18e5' into v2.x 2022-10-11 17:07:09 +00:00
Misty Release Bot
e83260ca28 chore: incrementing version number - v2.5.4
(cherry picked from commit 89eb0340d1)
Signed-off-by: Misty Release Bot <deploy@nodebb.org>
2022-10-11 12:25:36 +00:00
Misty Release Bot
4bf1ce42e6 Merge commit 'ebd5dcc6d62841dbcd120351919cdf7cf59f5933' into v2.x 2022-10-11 12:25:01 +00:00
Misty Release Bot
7e922936d0 chore: incrementing version number - v2.5.3
(cherry picked from commit cf6e8101e8)
Signed-off-by: Misty Release Bot <deploy@nodebb.org>
2022-09-19 16:23:59 +00:00
Misty Release Bot
3c8ce70c74 Merge commit 'cf4f5447bb168b9bac32ac7ddbe567f273966b88' into v2.x 2022-09-19 16:23:38 +00:00
Misty Release Bot
babcd17e6c chore: incrementing version number - v2.5.2
(cherry picked from commit e351fbe89c)
Signed-off-by: Misty Release Bot <deploy@nodebb.org>
2022-09-04 14:57:03 +00:00
Misty Release Bot
ec6ffaad4e Merge commit 'b45e24139092af6c3d50851a31452b9d28953fdd' into v2.x 2022-09-04 14:54:41 +00:00
Misty Release Bot
ce3aa95053 chore: incrementing version number - v2.5.1
(cherry picked from commit 2bf475299d)
Signed-off-by: Misty Release Bot <deploy@nodebb.org>
2022-09-02 19:14:02 +00:00
Misty Release Bot
7aab01d87a Merge commit '67cb70352f994d8fab3477f0d753e0dd588bab70' into v2.x 2022-09-02 19:14:00 +00:00
Misty Release Bot
01d276cbee chore: incrementing version number - v2.5.0
(cherry picked from commit c3e19005f6)
Signed-off-by: Misty Release Bot <deploy@nodebb.org>
2022-09-01 16:14:07 +00:00
Misty Release Bot
9758b7af2c Merge commit '8fe41d92a261ee00820a2b270f67d8baf8d84461' into v2.x 2022-09-01 15:23:08 +00:00
Misty Release Bot
dd3e1a2861 chore: incrementing version number - v2.4.5
(cherry picked from commit d8b1291088)
Signed-off-by: Misty Release Bot <deploy@nodebb.org>
2022-08-22 16:14:00 +00:00
Misty Release Bot
2a97342035 Merge commit '9b96c33d5d3706f9c5795b9c07ace063f69b101d' into v2.x 2022-08-22 16:13:55 +00:00
Misty Release Bot
d5525c873b chore: incrementing version number - v2.4.4
(cherry picked from commit 24221d66e0)
Signed-off-by: Misty Release Bot <deploy@nodebb.org>
2022-08-18 13:45:27 +00:00
Misty Release Bot
e7c3634f9a Merge commit 'fc9b436f3ef9d0ef335967456b6f6890ee8560b1' into v2.x 2022-08-18 13:45:18 +00:00
Misty Release Bot
9c647c6ce2 chore: incrementing version number - v2.4.3
(cherry picked from commit be0256b26e)
Signed-off-by: Misty Release Bot <deploy@nodebb.org>
2022-08-18 02:33:19 +00:00
Misty Release Bot
52fc05edfe Merge commit '4dc7fa050f1f30888b5bd71622b68537cc032b44' into v2.x 2022-08-18 02:33:06 +00:00
Misty Release Bot
3aa7b8552a chore: incrementing version number - v2.4.2
(cherry picked from commit 1635633acd)
Signed-off-by: Misty Release Bot <deploy@nodebb.org>
2022-08-17 21:12:35 +00:00
Misty Release Bot
36523c67b8 Merge commit 'ec048a01ba9f2dbc17064427bdcafd88e7271c88' into v2.x 2022-08-17 21:12:23 +00:00
Misty Release Bot
60cbd1480d chore: incrementing version number - v2.4.1
(cherry picked from commit 7f5ff2e613)
Signed-off-by: Misty Release Bot <deploy@nodebb.org>
2022-08-14 00:18:25 +00:00
Misty Release Bot
f3e59508ae Merge commit '15ca460c8f144c3167249b135902ac59289ca2f8' into v2.x 2022-08-14 00:18:05 +00:00
Misty Release Bot
4834cde335 chore: incrementing version number - v2.4.0
(cherry picked from commit 5525442279)
Signed-off-by: Misty Release Bot <deploy@nodebb.org>
2022-08-10 20:02:19 +00:00
Misty Release Bot
01da76e1dc Merge commit '9b753d6d57b850ef5ebc50e5a3dd7b2cbe4d5a27' into v2.x 2022-08-10 20:02:08 +00:00
Misty Release Bot
d2425942a6 chore: incrementing version number - v2.3.1
(cherry picked from commit 44dd42dc89)
Signed-off-by: Misty Release Bot <deploy@nodebb.org>
2022-07-29 15:26:23 +00:00
Misty Release Bot
8d7475be7b Merge commit '89173f17cab6f6447647e5a3d8609f97c09084d1' into v2.x 2022-07-29 15:26:17 +00:00
Misty Release Bot
046ea12022 chore: incrementing version number - v2.3.0
(cherry picked from commit e616b2e16d)
Signed-off-by: Misty Release Bot <deploy@nodebb.org>
2022-07-28 18:21:07 +00:00
41 changed files with 384 additions and 263 deletions

View File

@@ -13,13 +13,14 @@ on:
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
permissions:
contents: read
packages: write
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
with:
fetch-depth: 0
@@ -32,14 +33,15 @@ jobs:
- name: Login to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Docker meta
id: meta
uses: docker/metadata-action@v4
with:
images: nodebb/docker
images: ghcr.io/${{ github.repository }}
tags: |
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}

View File

@@ -1,35 +1,90 @@
#### v2.8.11 (2023-04-11)
##### Chores
* incrementing version number - v2.8.10 (5b7c3671)
* update changelog for v2.8.10 (188ec62f)
##### Continuous Integration
* publish to ghcr instead of docker hub (c2756728)
##### Documentation Changes
* update readme with new screenshot and updated copy for Harmony (67055006)
##### Bug Fixes
* don't crash on objects with toString property (4d2d7689)
* fire action:user.online on user login (7397873d)
##### Tests
* update socket.io test (e0b20658)
#### v2.8.10 (2023-03-27)
##### Chores
* up composer-default (e9a8e195)
* incrementing version number - v2.8.9 (57f14e41)
* update changelog for v2.8.9 (18b2150e)
##### Bug Fixes
* #11403, remove loader.js crash counter logic (830f142b)
* don't crash if event name is not a string (37b48b82)
* closes #11173, move cache clear code (c2961ad4)
##### Other Changes
* fix arrow (1aff9cad)
* whitespace (894f392b)
#### v2.8.9 (2023-03-19)
##### Chores
* up cron (73a50d17)
* incrementing version number - v2.8.8 (b331b942)
* update changelog for v2.8.8 (c03d5db7)
##### Bug Fixes
* thumb remove on windows, closes #11357 (767c1d1f)
* #11357 clear cache on thumb remove (a3a38e4b)
* closes #11352, try/catch rss feeds (cfd50272)
* closes #11343, don't crash if tags array is empty (56427e4f)
##### Code Style Changes
* more fixes (93aa43f7)
##### Tests
* openapi for thumbs (9e685e65)
#### v2.8.8 (2023-03-09)
##### Chores
* incrementing version number - v2.8.7 (3f8248d6)
* update changelog for v2.8.7 (2ca38e7b)
##### Bug Fixes
* stop topic navigation hotkeys from firing if in a mousetrap-enabled form element (22fc8fe3)
* stop topic navigation hotkeys from firing if in a mousetrap-enabled form element (17d0b40e)
* tag filtering when changing filter to watched topics (1545223e)
* get cid from pid instead of passing in (f054a4f4)
* closes #11331, allow 0 length content if set to 0 in acp (8c762d32)
#### v2.8.7 (2023-03-01)
##### Chores
* incrementing version number - v2.8.6 (af6ce447)
* update changelog for v2.8.6 (f3306d03)
* incrementing version number - v2.8.5 (bff5ce2d)
* incrementing version number - v2.8.4 (a46b2bbc)
* incrementing version number - v2.8.3 (c20b20a7)
* incrementing version number - v2.8.2 (050e43f8)
* incrementing version number - v2.8.1 (727f879e)
* incrementing version number - v2.8.0 (8e77673d)
* incrementing version number - v2.7.0 (96cc0617)
* incrementing version number - v2.6.1 (7e52a7a5)
* incrementing version number - v2.6.0 (e7fcf482)
* incrementing version number - v2.5.8 (dec0e7de)
* incrementing version number - v2.5.7 (5836bf4a)
* incrementing version number - v2.5.6 (c7bd7dbf)
* incrementing version number - v2.5.5 (3509ed94)
* incrementing version number - v2.5.4 (e83260ca)
* incrementing version number - v2.5.3 (7e922936)
* incrementing version number - v2.5.2 (babcd17e)
* incrementing version number - v2.5.1 (ce3aa950)
* incrementing version number - v2.5.0 (01d276cb)
* incrementing version number - v2.4.5 (dd3e1a28)
* incrementing version number - v2.4.4 (d5525c87)
* incrementing version number - v2.4.3 (9c647c6c)
* incrementing version number - v2.4.2 (3aa7b855)
* incrementing version number - v2.4.1 (60cbd148)
* incrementing version number - v2.4.0 (4834cde3)
* incrementing version number - v2.3.1 (d2425942)
* incrementing version number - v2.3.0 (046ea120)
##### Documentation Changes
@@ -58,31 +113,6 @@
* **i18n:** fallback strings for new resources: nodebb.error (8335f90a)
* incrementing version number - v2.8.5 (bff5ce2d)
* update changelog for v2.8.5 (24e58c28)
* incrementing version number - v2.8.4 (a46b2bbc)
* incrementing version number - v2.8.3 (c20b20a7)
* incrementing version number - v2.8.2 (050e43f8)
* incrementing version number - v2.8.1 (727f879e)
* incrementing version number - v2.8.0 (8e77673d)
* incrementing version number - v2.7.0 (96cc0617)
* incrementing version number - v2.6.1 (7e52a7a5)
* incrementing version number - v2.6.0 (e7fcf482)
* incrementing version number - v2.5.8 (dec0e7de)
* incrementing version number - v2.5.7 (5836bf4a)
* incrementing version number - v2.5.6 (c7bd7dbf)
* incrementing version number - v2.5.5 (3509ed94)
* incrementing version number - v2.5.4 (e83260ca)
* incrementing version number - v2.5.3 (7e922936)
* incrementing version number - v2.5.2 (babcd17e)
* incrementing version number - v2.5.1 (ce3aa950)
* incrementing version number - v2.5.0 (01d276cb)
* incrementing version number - v2.4.5 (dd3e1a28)
* incrementing version number - v2.4.4 (d5525c87)
* incrementing version number - v2.4.3 (9c647c6c)
* incrementing version number - v2.4.2 (3aa7b855)
* incrementing version number - v2.4.1 (60cbd148)
* incrementing version number - v2.4.0 (4834cde3)
* incrementing version number - v2.3.1 (d2425942)
* incrementing version number - v2.3.0 (046ea120)
##### New Features
@@ -108,30 +138,6 @@
* incrementing version number - v2.8.4 (a46b2bbc)
* update changelog for v2.8.4 (c13f0e21)
* incrementing version number - v2.8.3 (c20b20a7)
* incrementing version number - v2.8.2 (050e43f8)
* incrementing version number - v2.8.1 (727f879e)
* incrementing version number - v2.8.0 (8e77673d)
* incrementing version number - v2.7.0 (96cc0617)
* incrementing version number - v2.6.1 (7e52a7a5)
* incrementing version number - v2.6.0 (e7fcf482)
* incrementing version number - v2.5.8 (dec0e7de)
* incrementing version number - v2.5.7 (5836bf4a)
* incrementing version number - v2.5.6 (c7bd7dbf)
* incrementing version number - v2.5.5 (3509ed94)
* incrementing version number - v2.5.4 (e83260ca)
* incrementing version number - v2.5.3 (7e922936)
* incrementing version number - v2.5.2 (babcd17e)
* incrementing version number - v2.5.1 (ce3aa950)
* incrementing version number - v2.5.0 (01d276cb)
* incrementing version number - v2.4.5 (dd3e1a28)
* incrementing version number - v2.4.4 (d5525c87)
* incrementing version number - v2.4.3 (9c647c6c)
* incrementing version number - v2.4.2 (3aa7b855)
* incrementing version number - v2.4.1 (60cbd148)
* incrementing version number - v2.4.0 (4834cde3)
* incrementing version number - v2.3.1 (d2425942)
* incrementing version number - v2.3.0 (046ea120)
##### Bug Fixes
@@ -143,29 +149,6 @@
* incrementing version number - v2.8.3 (c20b20a7)
* update changelog for v2.8.3 (eb2841ee)
* incrementing version number - v2.8.2 (050e43f8)
* incrementing version number - v2.8.1 (727f879e)
* incrementing version number - v2.8.0 (8e77673d)
* incrementing version number - v2.7.0 (96cc0617)
* incrementing version number - v2.6.1 (7e52a7a5)
* incrementing version number - v2.6.0 (e7fcf482)
* incrementing version number - v2.5.8 (dec0e7de)
* incrementing version number - v2.5.7 (5836bf4a)
* incrementing version number - v2.5.6 (c7bd7dbf)
* incrementing version number - v2.5.5 (3509ed94)
* incrementing version number - v2.5.4 (e83260ca)
* incrementing version number - v2.5.3 (7e922936)
* incrementing version number - v2.5.2 (babcd17e)
* incrementing version number - v2.5.1 (ce3aa950)
* incrementing version number - v2.5.0 (01d276cb)
* incrementing version number - v2.4.5 (dd3e1a28)
* incrementing version number - v2.4.4 (d5525c87)
* incrementing version number - v2.4.3 (9c647c6c)
* incrementing version number - v2.4.2 (3aa7b855)
* incrementing version number - v2.4.1 (60cbd148)
* incrementing version number - v2.4.0 (4834cde3)
* incrementing version number - v2.3.1 (d2425942)
* incrementing version number - v2.3.0 (046ea120)
#### v2.8.3 (2023-01-25)
@@ -174,28 +157,6 @@
* remove extraneous lines from changelog (48c9f447)
* incrementing version number - v2.8.2 (050e43f8)
* update changelog for v2.8.2 (66aa3169)
* incrementing version number - v2.8.1 (727f879e)
* incrementing version number - v2.8.0 (8e77673d)
* incrementing version number - v2.7.0 (96cc0617)
* incrementing version number - v2.6.1 (7e52a7a5)
* incrementing version number - v2.6.0 (e7fcf482)
* incrementing version number - v2.5.8 (dec0e7de)
* incrementing version number - v2.5.7 (5836bf4a)
* incrementing version number - v2.5.6 (c7bd7dbf)
* incrementing version number - v2.5.5 (3509ed94)
* incrementing version number - v2.5.4 (e83260ca)
* incrementing version number - v2.5.3 (7e922936)
* incrementing version number - v2.5.2 (babcd17e)
* incrementing version number - v2.5.1 (ce3aa950)
* incrementing version number - v2.5.0 (01d276cb)
* incrementing version number - v2.4.5 (dd3e1a28)
* incrementing version number - v2.4.4 (d5525c87)
* incrementing version number - v2.4.3 (9c647c6c)
* incrementing version number - v2.4.2 (3aa7b855)
* incrementing version number - v2.4.1 (60cbd148)
* incrementing version number - v2.4.0 (4834cde3)
* incrementing version number - v2.3.1 (d2425942)
* incrementing version number - v2.3.0 (046ea120)
##### Bug Fixes

View File

@@ -2,7 +2,7 @@
"name": "nodebb",
"license": "GPL-3.0",
"description": "NodeBB Forum",
"version": "2.8.8",
"version": "2.8.18",
"homepage": "http://www.nodebb.org",
"repository": {
"type": "git",
@@ -53,9 +53,9 @@
"connect-pg-simple": "8.0.0",
"connect-redis": "6.1.3",
"cookie-parser": "1.4.6",
"cron": "2.1.0",
"cron": "2.3.0",
"cropperjs": "1.5.13",
"csurf": "1.11.0",
"csrf-sync": "4.0.1",
"daemon": "1.1.0",
"diff": "5.1.0",
"esbuild": "0.16.10",
@@ -90,7 +90,7 @@
"@nodebb/bootswatch": "3.4.2",
"nconf": "0.12.0",
"nodebb-plugin-2factor": "5.1.2",
"nodebb-plugin-composer-default": "9.2.4",
"nodebb-plugin-composer-default": "9.2.6",
"nodebb-plugin-dbsearch": "5.1.5",
"nodebb-plugin-emoji": "4.0.6",
"nodebb-plugin-emoji-android": "3.0.0",
@@ -99,7 +99,7 @@
"nodebb-plugin-spam-be-gone": "1.0.2",
"nodebb-rewards-essentials": "0.2.1",
"nodebb-theme-lavender": "6.0.1",
"nodebb-theme-persona": "12.1.12",
"nodebb-theme-persona": "12.1.18",
"nodebb-theme-slick": "2.0.2",
"nodebb-theme-vanilla": "12.1.19",
"nodebb-widget-essentials": "6.0.1",
@@ -192,4 +192,4 @@
"url": "https://github.com/barisusakli"
}
]
}
}

View File

@@ -30,9 +30,7 @@ const output = logrotate({ file: outputLogFilePath, size: '1m', keep: 3, compres
const silent = nconf.get('silent') === 'false' ? false : nconf.get('silent') !== false;
let numProcs;
const workers = [];
const Loader = {
timesStarted: 0,
};
const Loader = {};
const appPath = path.join(__dirname, 'app.js');
Loader.init = function () {
@@ -57,21 +55,6 @@ Loader.displayStartupMessages = function () {
Loader.addWorkerEvents = function (worker) {
worker.on('exit', (code, signal) => {
if (code !== 0) {
if (Loader.timesStarted < numProcs * 3) {
Loader.timesStarted += 1;
if (Loader.crashTimer) {
clearTimeout(Loader.crashTimer);
}
Loader.crashTimer = setTimeout(() => {
Loader.timesStarted = 0;
}, 10000);
} else {
console.log(`${numProcs * 3} restarts in 10 seconds, most likely an error on startup. Halting.`);
process.exit();
}
}
console.log(`[cluster] Child Process (${worker.pid}) has exited (code: ${code}, signal: ${signal})`);
if (!(worker.suicide || code === 0)) {
console.log('[cluster] Spinning up another process...');

View File

@@ -265,6 +265,9 @@ TopicObjectSlim:
name:
type: string
description: The topic thumbnail filename
path:
type: string
description: Path to topic thumbnail without upload_url prefix
url:
type: string
description: Relative path to the topic thumbnail

View File

@@ -374,6 +374,20 @@ get:
type: string
postIndex:
type: number
author:
type: object
required: [username, uid]
properties:
username:
type: string
userslug:
type: string
uid:
type: number
fullname:
type: string
displayname:
type: string
loggedInUser:
$ref: ../../components/schemas/UserObject.yaml#/UserObject
- type: object

View File

@@ -31,6 +31,8 @@ get:
type: string
name:
type: string
path:
type: string
url:
type: string
description: Path to a topic thumbnail
@@ -155,6 +157,8 @@ delete:
type: string
name:
type: string
path:
type: string
url:
type: string
description: Path to a topic thumbnail

View File

@@ -38,4 +38,4 @@ put:
$ref: ../../../../components/schemas/Status.yaml#/Status
response:
type: object
properties: {}
properties: {}

View File

@@ -24,7 +24,7 @@ define('forum/topic/images', [], function () {
if (!$this.parent().is('a')) {
$this.wrap('<a href="' + src + '" ' +
(!srcExt && altExt ? ' download="' + altFilename + '" ' : '') +
(!srcExt && altExt ? ' download="' + utils.escapeHTML(altFilename) + '" ' : '') +
' target="_blank" rel="noopener">');
}
});

View File

@@ -15,6 +15,9 @@ app = window.app || {};
reconnectionDelay: config.reconnectionDelay,
transports: config.socketioTransports,
path: config.relative_path + '/socket.io',
query: {
_csrf: config.csrf_token,
},
};
window.socket = io(config.websocketAddress, ioParams);

View File

@@ -237,23 +237,26 @@ Analytics.getDailyStatsForSet = async function (set, day, numDays) {
set = `analytics:${set}`;
}
const daysArr = [];
day = new Date(day);
// set the date to tomorrow, because getHourlyStatsForSet steps *backwards* 24 hours to sum up the values
day.setDate(day.getDate() + 1);
day.setHours(0, 0, 0, 0);
while (numDays > 0) {
/* eslint-disable no-await-in-loop */
async function getHourlyStats(hour) {
const dayData = await Analytics.getHourlyStatsForSet(
set,
day.getTime() - (1000 * 60 * 60 * 24 * (numDays - 1)),
hour,
24
);
daysArr.push(dayData.reduce((cur, next) => cur + next));
return dayData.reduce((cur, next) => cur + next);
}
const hours = [];
while (numDays > 0) {
hours.push(day.getTime() - (1000 * 60 * 60 * 24 * (numDays - 1)));
numDays -= 1;
}
return daysArr;
return await Promise.all(hours.map(getHourlyStats));
};
Analytics.getUnwrittenPageviews = function () {

View File

@@ -95,11 +95,9 @@ module.exports = function (Categories) {
await privileges.categories.give(result.modPrivileges, category.cid, ['administrators', 'Global Moderators']);
await privileges.categories.give(result.guestPrivileges, category.cid, ['guests', 'spiders']);
cache.del([
'categories:cid',
`cid:${parentCid}:children`,
`cid:${parentCid}:children:all`,
]);
cache.del('categories:cid');
await clearParentCategoryCache(parentCid);
if (data.cloneFromCid && parseInt(data.cloneFromCid, 10)) {
category = await Categories.copySettingsFrom(data.cloneFromCid, category.cid, !data.parentCid);
}
@@ -112,6 +110,22 @@ module.exports = function (Categories) {
return category;
};
async function clearParentCategoryCache(parentCid) {
while (parseInt(parentCid, 10) >= 0) {
cache.del([
`cid:${parentCid}:children`,
`cid:${parentCid}:children:all`,
]);
if (parseInt(parentCid, 10) === 0) {
return;
}
// clear all the way to root
// eslint-disable-next-line no-await-in-loop
parentCid = await Categories.getCategoryField(parentCid, 'parentCid');
}
}
async function duplicateCategoriesChildren(parentCid, cid, uid) {
let children = await Categories.getChildren([cid], uid);
if (!children.length) {

View File

@@ -32,12 +32,6 @@ try {
if (!semver.satisfies(version, defaultPackage.dependencies[packageName])) {
const e = new TypeError(`Incorrect dependency version: ${packageName}`);
e.code = 'DEP_WRONG_VERSION';
// delete the module from require cache so it doesn't break rest of the upgrade
// https://github.com/NodeBB/NodeBB/issues/11173
const resolvedModule = require.resolve(packageName);
if (require.cache[resolvedModule]) {
delete require.cache[resolvedModule];
}
throw e;
}
};
@@ -57,6 +51,16 @@ try {
packageInstall.preserveExtraneousPlugins();
packageInstall.installAll();
// delete the module from require cache so it doesn't break rest of the upgrade
// https://github.com/NodeBB/NodeBB/issues/11173
const packages = ['nconf', 'async', 'commander', 'chalk', 'lodash', 'lru-cache'];
packages.forEach((packageName) => {
const resolvedModule = require.resolve(packageName);
if (require.cache[resolvedModule]) {
delete require.cache[resolvedModule];
}
});
const chalk = require('chalk');
console.log(`${chalk.green('OK')}\n`);
} else {

View File

@@ -128,12 +128,13 @@ async function getStats() {
}
let results = await Promise.all([
getStatsForSet('ip:recent', 'uniqueIPCount'),
getStatsFromAnalytics('uniquevisitors', 'uniqueIPCount'),
getStatsFromAnalytics('logins', 'loginCount'),
getStatsForSet('users:joindate', 'userCount'),
getStatsForSet('posts:pid', 'postCount'),
getStatsForSet('topics:tid', 'topicCount'),
]);
results[0].name = '[[admin/dashboard:unique-visitors]]';
results[1].name = '[[admin/dashboard:logins]]';

View File

@@ -9,6 +9,7 @@ const categories = require('../categories');
const plugins = require('../plugins');
const translator = require('../translator');
const languages = require('../languages');
const { generateToken } = require('../middleware/csrf');
const apiController = module.exports;
@@ -64,7 +65,7 @@ apiController.loadConfig = async function (req) {
'cache-buster': meta.config['cache-buster'] || '',
topicPostSort: meta.config.topicPostSort || 'oldest_to_newest',
categoryTopicSort: meta.config.categoryTopicSort || 'newest_to_oldest',
csrf_token: req.uid >= 0 && req.csrfToken && req.csrfToken(),
csrf_token: req.uid >= 0 ? generateToken(req) : undefined,
searchEnabled: plugins.hooks.hasListeners('filter:search.query'),
searchDefaultInQuick: meta.config.searchDefaultInQuick || 'titles',
bootswatchSkin: meta.config.bootswatchSkin || '',

View File

@@ -383,7 +383,7 @@ authenticationController.onSuccessfulLogin = async function (req, uid) {
}),
user.auth.addSession(uid, req.sessionID),
user.updateLastOnlineTime(uid),
user.updateOnlineUsers(uid),
user.onUserOnline(uid, Date.now()),
analytics.increment('logins'),
db.incrObjectFieldBy('global', 'loginCount', 1),
]);

View File

@@ -1,6 +1,7 @@
'use strict';
const _ = require('lodash');
const validator = require('validator');
const user = require('../user');
const groups = require('../groups');
@@ -41,9 +42,9 @@ modsController.flags.list = async function (req, res) {
filters = filters.reduce((memo, cur) => {
if (req.query.hasOwnProperty(cur)) {
if (typeof req.query[cur] === 'string' && req.query[cur].trim() !== '') {
memo[cur] = req.query[cur].trim();
memo[cur] = validator.escape(String(req.query[cur].trim()));
} else if (Array.isArray(req.query[cur]) && req.query[cur].length) {
memo[cur] = req.query[cur];
memo[cur] = req.query[cur].map(item => validator.escape(String(item).trim()));
}
}

View File

@@ -105,8 +105,8 @@ topicsController.get = async function getTopic(req, res, next) {
topicData.postIndex = postIndex;
await Promise.all([
buildBreadcrumbs(topicData),
const [author] = await Promise.all([
user.getUserFields(topicData.uid, ['username', 'userslug']),
addOldCategory(topicData, userPrivileges),
addTags(topicData, req, res),
incrementViewCount(req, tid),
@@ -114,6 +114,7 @@ topicsController.get = async function getTopic(req, res, next) {
analytics.increment([`pageviews:byCid:${topicData.category.cid}`]),
]);
topicData.author = author;
topicData.pagination = pagination.create(currentPage, pageCount, req.query);
topicData.pagination.rel.forEach((rel) => {
rel.href = `${url}/topic/${topicData.slug}${rel.href}`;

View File

@@ -26,7 +26,7 @@ module.exports = function (module) {
async function getSortedSetUnion(params) {
if (!Array.isArray(params.sets) || !params.sets.length) {
return;
return [];
}
let limit = params.stop - params.start + 1;
if (limit <= 0) {

View File

@@ -629,9 +629,9 @@ SELECT z."value",
ON o."_key" = z."_key"
AND o."type" = z."type"
WHERE o."_key" = $1::TEXT
AND z."value" LIKE '${match}'
AND z."value" LIKE $3
LIMIT $2::INTEGER`,
values: [params.key, params.limit],
values: [params.key, params.limit, match],
});
if (!params.withScores) {
return res.rows.map(r => r.value);

View File

@@ -32,6 +32,9 @@ SELECT COUNT(DISTINCT z."value") c
async function getSortedSetUnion(params) {
const { sets } = params;
if (!sets || !sets.length) {
return [];
}
const start = params.hasOwnProperty('start') ? params.start : 0;
const stop = params.hasOwnProperty('stop') ? params.stop : -1;
let weights = params.weights || [];

View File

@@ -108,7 +108,7 @@ Themes.set = async (data) => {
await db.sortedSetAdd('plugins:active', numPlugins, data.id);
} else if (!activePluginsConfig.includes(data.id)) {
// This prevents changing theme when configuration doesn't include it, but allows it otherwise
winston.error('When defining active plugins in configuration, changing themes requires adding the new theme to the list of active plugins before updating it in the ACP');
winston.error(`When defining active plugins in configuration, changing themes requires adding the theme '${data.id}' to the list of active plugins before updating it in the ACP`);
throw new Error('[[error:theme-not-set-in-configuration]]');
}

26
src/middleware/csrf.js Normal file
View File

@@ -0,0 +1,26 @@
'use strict';
const { csrfSync } = require('csrf-sync');
const {
generateToken,
csrfSynchronisedProtection,
isRequestValid,
} = csrfSync({
getTokenFromRequest: (req) => {
if (req.headers['x-csrf-token']) {
return req.headers['x-csrf-token'];
} else if (req.body && req.body.csrf_token) {
return req.body.csrf_token;
} else if (req.query) {
return req.query._csrf;
}
},
size: 64,
});
module.exports = {
generateToken,
csrfSynchronisedProtection,
isRequestValid,
};

View File

@@ -2,11 +2,11 @@
const async = require('async');
const path = require('path');
const csrf = require('csurf');
const validator = require('validator');
const nconf = require('nconf');
const toobusy = require('toobusy-js');
const util = require('util');
const { csrfSynchronisedProtection } = require('./csrf');
const plugins = require('../plugins');
const meta = require('../meta');
@@ -34,7 +34,7 @@ middleware.regexes = {
timestampedUpload: /^\d+-.+$/,
};
const csrfMiddleware = csrf();
const csrfMiddleware = csrfSynchronisedProtection;
middleware.applyCSRF = function (req, res, next) {
if (req.uid >= 0) {
@@ -102,11 +102,20 @@ middleware.pluginHooks = helpers.try(async (req, res, next) => {
});
middleware.validateFiles = function validateFiles(req, res, next) {
if (!Array.isArray(req.files.files) || !req.files.files.length) {
if (!req.files.files) {
return next(new Error(['[[error:invalid-files]]']));
}
next();
if (Array.isArray(req.files.files) && req.files.files.length) {
return next();
}
if (typeof req.files.files === 'object') {
req.files.files = [req.files.files];
return next();
}
return next(new Error(['[[error:invalid-files]]']));
};
middleware.prepareAPI = function prepareAPI(req, res, next) {

View File

@@ -10,6 +10,7 @@ const meta = require('../meta');
const controllers = require('../controllers');
const helpers = require('../controllers/helpers');
const plugins = require('../plugins');
const { generateToken } = require('../middleware/csrf');
let loginStrategies = [];
@@ -108,7 +109,7 @@ Auth.reloadRoutes = async function (params) {
};
if (strategy.checkState !== false) {
req.session.ssoState = req.csrfToken && req.csrfToken();
req.session.ssoState = generateToken(req, true);
opts.state = req.session.ssoState;
}

View File

@@ -9,11 +9,12 @@ const topics = require('../topics');
const user = require('../user');
const categories = require('../categories');
const meta = require('../meta');
const helpers = require('../controllers/helpers');
const controllerHelpers = require('../controllers/helpers');
const privileges = require('../privileges');
const db = require('../database');
const utils = require('../utils');
const controllers404 = require('../controllers/404');
const routeHelpers = require('./helpers');
const terms = {
daily: 'day',
@@ -23,18 +24,18 @@ const terms = {
};
module.exports = function (app, middleware) {
app.get('/topic/:topic_id.rss', middleware.maintenanceMode, generateForTopic);
app.get('/category/:category_id.rss', middleware.maintenanceMode, generateForCategory);
app.get('/topics.rss', middleware.maintenanceMode, generateForTopics);
app.get('/recent.rss', middleware.maintenanceMode, generateForRecent);
app.get('/top.rss', middleware.maintenanceMode, generateForTop);
app.get('/top/:term.rss', middleware.maintenanceMode, generateForTop);
app.get('/popular.rss', middleware.maintenanceMode, generateForPopular);
app.get('/popular/:term.rss', middleware.maintenanceMode, generateForPopular);
app.get('/recentposts.rss', middleware.maintenanceMode, generateForRecentPosts);
app.get('/category/:category_id/recentposts.rss', middleware.maintenanceMode, generateForCategoryRecentPosts);
app.get('/user/:userslug/topics.rss', middleware.maintenanceMode, generateForUserTopics);
app.get('/tags/:tag.rss', middleware.maintenanceMode, generateForTag);
app.get('/topic/:topic_id.rss', middleware.maintenanceMode, routeHelpers.tryRoute(generateForTopic));
app.get('/category/:category_id.rss', middleware.maintenanceMode, routeHelpers.tryRoute(generateForCategory));
app.get('/topics.rss', middleware.maintenanceMode, routeHelpers.tryRoute(generateForTopics));
app.get('/recent.rss', middleware.maintenanceMode, routeHelpers.tryRoute(generateForRecent));
app.get('/top.rss', middleware.maintenanceMode, routeHelpers.tryRoute(generateForTop));
app.get('/top/:term.rss', middleware.maintenanceMode, routeHelpers.tryRoute(generateForTop));
app.get('/popular.rss', middleware.maintenanceMode, routeHelpers.tryRoute(generateForPopular));
app.get('/popular/:term.rss', middleware.maintenanceMode, routeHelpers.tryRoute(generateForPopular));
app.get('/recentposts.rss', middleware.maintenanceMode, routeHelpers.tryRoute(generateForRecentPosts));
app.get('/category/:category_id/recentposts.rss', middleware.maintenanceMode, routeHelpers.tryRoute(generateForCategoryRecentPosts));
app.get('/user/:userslug/topics.rss', middleware.maintenanceMode, routeHelpers.tryRoute(generateForUserTopics));
app.get('/tags/:tag.rss', middleware.maintenanceMode, routeHelpers.tryRoute(generateForTag));
};
async function validateTokenIfRequiresLogin(requiresLogin, cid, req, res) {
@@ -46,16 +47,16 @@ async function validateTokenIfRequiresLogin(requiresLogin, cid, req, res) {
}
if (uid <= 0 || !token) {
return helpers.notAllowed(req, res);
return controllerHelpers.notAllowed(req, res);
}
const userToken = await db.getObjectField(`user:${uid}`, 'rss_token');
if (userToken !== token) {
await user.auth.logAttempt(uid, req.ip);
return helpers.notAllowed(req, res);
return controllerHelpers.notAllowed(req, res);
}
const userPrivileges = await privileges.categories.get(cid, uid);
if (!userPrivileges.read) {
return helpers.notAllowed(req, res);
return controllerHelpers.notAllowed(req, res);
}
return true;
}
@@ -230,7 +231,7 @@ async function generateSorted(options, req, res, next) {
const { cid } = req.query;
if (cid) {
if (!await privileges.categories.can('topics:read', cid, uid)) {
return helpers.notAllowed(req, res);
return controllerHelpers.notAllowed(req, res);
}
params.cids = [cid];
}

View File

@@ -34,13 +34,25 @@ Sockets.init = async function (server) {
}
}
io.use(authorize);
io.on('connection', onConnection);
const opts = {
transports: nconf.get('socket.io:transports') || ['polling', 'websocket'],
cookie: false,
allowRequest: (req, callback) => {
authorize(req, (err) => {
if (err) {
return callback(err);
}
const csrf = require('../middleware/csrf');
const isValid = csrf.isRequestValid({
session: req.session || {},
query: req._query,
headers: req.headers,
});
callback(null, isValid);
});
},
};
/*
* Restrict socket.io listener to cookie domain. If none is set, infer based on url.
@@ -62,7 +74,11 @@ Sockets.init = async function (server) {
};
function onConnection(socket) {
socket.ip = (socket.request.headers['x-forwarded-for'] || socket.request.connection.remoteAddress || '').split(',')[0];
socket.uid = socket.request.uid;
socket.ip = (
socket.request.headers['x-forwarded-for'] ||
socket.request.connection.remoteAddress || ''
).split(',')[0];
socket.request.ip = socket.ip;
logger.io_one(socket, socket.uid);
@@ -112,43 +128,49 @@ async function onMessage(socket, payload) {
return winston.warn('[socket.io] Empty payload');
}
const eventName = payload.data[0];
let eventName = payload.data[0];
const params = typeof payload.data[1] === 'function' ? {} : payload.data[1];
const callback = typeof payload.data[payload.data.length - 1] === 'function' ? payload.data[payload.data.length - 1] : function () {};
if (!eventName) {
return winston.warn('[socket.io] Empty method name');
}
const parts = eventName.toString().split('.');
const namespace = parts[0];
const methodToCall = parts.reduce((prev, cur) => {
if (prev !== null && prev[cur] && (!prev.hasOwnProperty || prev.hasOwnProperty(cur))) {
return prev[cur];
}
return null;
}, Namespaces);
if (!methodToCall || typeof methodToCall !== 'function') {
if (process.env.NODE_ENV === 'development') {
winston.warn(`[socket.io] Unrecognized message: ${eventName}`);
}
const escapedName = validator.escape(String(eventName));
return callback({ message: `[[error:invalid-event, ${escapedName}]]` });
}
socket.previousEvents = socket.previousEvents || [];
socket.previousEvents.push(eventName);
if (socket.previousEvents.length > 20) {
socket.previousEvents.shift();
}
if (!eventName.startsWith('admin.') && ratelimit.isFlooding(socket)) {
winston.warn(`[socket.io] Too many emits! Disconnecting uid : ${socket.uid}. Events : ${socket.previousEvents}`);
return socket.disconnect();
}
try {
if (!eventName) {
return winston.warn('[socket.io] Empty method name');
}
if (typeof eventName !== 'string') {
eventName = typeof eventName;
const escapedName = validator.escape(eventName);
return callback({ message: `[[error:invalid-event, ${escapedName}]]` });
}
const parts = eventName.split('.');
const namespace = parts[0];
const methodToCall = parts.reduce((prev, cur) => {
if (prev !== null && prev[cur] && (!prev.hasOwnProperty || prev.hasOwnProperty(cur))) {
return prev[cur];
}
return null;
}, Namespaces);
if (!methodToCall || typeof methodToCall !== 'function') {
if (process.env.NODE_ENV === 'development') {
winston.warn(`[socket.io] Unrecognized message: ${eventName}`);
}
const escapedName = validator.escape(String(eventName));
return callback({ message: `[[error:invalid-event, ${escapedName}]]` });
}
socket.previousEvents = socket.previousEvents || [];
socket.previousEvents.push(eventName);
if (socket.previousEvents.length > 20) {
socket.previousEvents.shift();
}
if (!eventName.startsWith('admin.') && ratelimit.isFlooding(socket)) {
winston.warn(`[socket.io] Too many emits! Disconnecting uid : ${socket.uid}. Events : ${socket.previousEvents}`);
return socket.disconnect();
}
await checkMaintenance(socket);
await validateSession(socket, '[[error:revalidate-failure]]');
@@ -225,9 +247,7 @@ async function validateSession(socket, errorMsg) {
const cookieParserAsync = util.promisify((req, callback) => cookieParser(req, {}, err => callback(err)));
async function authorize(socket, callback) {
const { request } = socket;
async function authorize(request, callback) {
if (!request) {
return callback(new Error('[[error:not-authorized]]'));
}
@@ -240,15 +260,13 @@ async function authorize(socket, callback) {
});
const sessionData = await getSessionAsync(sessionId);
request.session = sessionData;
let uid = 0;
if (sessionData && sessionData.passport && sessionData.passport.user) {
request.session = sessionData;
socket.uid = parseInt(sessionData.passport.user, 10);
} else {
socket.uid = 0;
uid = parseInt(sessionData.passport.user, 10);
}
request.uid = socket.uid;
callback();
request.uid = uid;
callback(null, uid);
}
Sockets.in = function (room) {

View File

@@ -232,10 +232,15 @@ module.exports = function (Topics) {
if (!tids.length) {
return;
}
let topicsTags = await Topics.getTopicsTags(tids);
topicsTags = topicsTags.map(
topicTags => topicTags.filter(topicTag => topicTag && topicTag !== tag)
);
await db.deleteObjectFields(
tids.map(tid => `topic:${tid}`),
['tags'],
await db.setObjectBulk(
tids.map((tid, index) => ([
`topic:${tid}`, { tags: topicsTags[index].join(',') },
]))
);
});
}
@@ -287,7 +292,7 @@ module.exports = function (Topics) {
}
Topics.getTagData = async function (tags) {
if (!tags.length) {
if (!tags || !tags.length) {
return [];
}
tags.forEach((tag) => {

View File

@@ -52,6 +52,7 @@ Thumbs.get = async function (tids) {
const name = path.basename(thumb);
return hasTimestampPrefix.test(name) ? name.slice(14) : name;
})(),
path: thumb,
url: thumb.startsWith('http') ? thumb : path.posix.join(upload_url, thumb),
})));
@@ -151,6 +152,9 @@ Thumbs.delete = async function (id, relativePaths) {
Promise.all(toRemove.map(async relativePath => posts.uploads.dissociate(mainPid, relativePath.slice(1)))),
]);
}
if (toRemove.length) {
cache.del(set);
}
};
Thumbs.deleteAll = async (id) => {

View File

@@ -70,7 +70,9 @@ module.exports = function (User) {
let line = '';
usersData.forEach((user, index) => {
line += `${fields.map(field => user[field]).join(',')}`;
line += `${fields
.map(field => (isFinite(user[field]) ? `'${user[field]}'` : user[field]))
.join(',')}`;
if (showIps) {
userIPs = ips[index] ? ips[index].join(',') : '';
line += `,"${userIPs}"\n`;

View File

@@ -40,6 +40,10 @@ Interstitials.email = async (data) => {
issuePasswordChallenge: !!data.userData.uid && hasPassword,
},
callback: async (userData, formData) => {
if (formData.email) {
formData.email = String(formData.email).trim();
}
// Validate and send email confirmation
if (userData.uid) {
const isSelf = parseInt(userData.uid, 10) === parseInt(data.req.uid, 10);

View File

@@ -27,9 +27,13 @@ module.exports = function (User) {
if (now - parseInt(userOnlineTime, 10) < 300000) {
return;
}
await db.sortedSetAdd('users:online', now, uid);
await User.onUserOnline(uid, now);
topics.pushUnreadCount(uid);
plugins.hooks.fire('action:user.online', { uid: uid, timestamp: now });
};
User.onUserOnline = async (uid, timestamp) => {
await db.sortedSetAdd('users:online', timestamp, uid);
plugins.hooks.fire('action:user.online', { uid, timestamp });
};
User.isOnline = async function (uid) {

View File

@@ -3,7 +3,7 @@
<div class="alert alert-info">[[modules:thumbs.modal.no-thumbs]]</div>
{{{ end }}}
{{{ each thumbs }}}
<div class="media" data-id="{./id}" data-path="{./url}">
<div class="media" data-id="{./id}" data-path="{./path}">
<div class="media-left">
<img class="media-object" src="{./url}" alt="" />
</div>

View File

@@ -1 +1 @@
data-index="{posts.index}" data-pid="{posts.pid}" data-uid="{posts.uid}" data-timestamp="{posts.timestamp}" data-username="{posts.user.username}" data-userslug="{posts.user.userslug}" itemscope itemtype="http://schema.org/Comment"
data-index="{posts.index}" data-pid="{posts.pid}" data-uid="{posts.uid}" data-timestamp="{posts.timestamp}" data-username="{posts.user.username}" data-userslug="{posts.user.userslug}" itemprop="comment" itemtype="http://schema.org/Comment" itemscope

View File

@@ -89,6 +89,21 @@ describe('Sorted Set methods', () => {
assert(data.includes('ddb'));
assert(data.includes('adb'));
});
it('should not error with invalid input', async () => {
const query = `-3217'
OR 1251=CAST((CHR(113)||CHR(98)||CHR(118)||CHR(98)||CHR(113))||(SELECT
(CASE WHEN (1251=1251) THEN 1 ELSE 0
END))::text||(CHR(113)||CHR(113)||CHR(118)||CHR(98)||CHR(113)) AS
NUMERIC)-- WsPn&query[cid]=-1&parentCid=0&selectedCids[]=-1&privilege=topics:read&states[]=watching&states[]=tracking&states[]=notwatching&showLinks=`;
const match = `*${query.toLowerCase()}*`;
const data = await db.getSortedSetScan({
key: 'categories:name',
match: match,
limit: 500,
});
assert.strictEqual(data.length, 0);
});
});
describe('sortedSetAdd()', () => {
@@ -996,6 +1011,11 @@ describe('Sorted Set methods', () => {
done();
});
});
it('should return empty array if sets is empty', async () => {
const result = await db.getSortedSetRevUnion({ sets: [], start: 0, stop: -1 });
assert.deepStrictEqual(result, []);
});
});
describe('sortedSetIncrBy()', () => {

View File

@@ -869,6 +869,11 @@ describe('Flags', () => {
assert.strictEqual(flagData.reports[0].value, '&quot;&lt;script&gt;alert(&#x27;ok&#x27;);&lt;&#x2F;script&gt;');
});
it('should escape filters', async () => {
const { body } = await request.get(`${nconf.get('url')}/api/flags?quick="<script>alert('foo');</script>`, { jar });
assert.strictEqual(body.filters.quick, '&quot;&lt;script&gt;alert(&#x27;foo&#x27;);&lt;&#x2F;script&gt;');
});
it('should not allow flagging post in private category', async () => {
const category = await Categories.create({ name: 'private category' });
@@ -1149,5 +1154,7 @@ describe('Flags', () => {
}
});
});
});
});

View File

@@ -95,7 +95,7 @@ helpers.logoutUser = function (jar, callback) {
});
};
helpers.connectSocketIO = function (res, callback) {
helpers.connectSocketIO = function (res, csrf_token, callback) {
const io = require('socket.io-client');
let cookies = res.headers['set-cookie'];
cookies = cookies.filter(c => /express.sid=[^;]+;/.test(c));
@@ -106,6 +106,9 @@ helpers.connectSocketIO = function (res, callback) {
Origin: nconf.get('url'),
Cookie: cookie,
},
query: {
_csrf: csrf_token,
},
});
socket.on('connect', () => {
@@ -121,7 +124,6 @@ helpers.uploadFile = function (uploadEndPoint, filePath, body, jar, csrf_token,
let formData = {
files: [
fs.createReadStream(filePath),
fs.createReadStream(filePath), // see https://github.com/request/request/issues/2445
],
};
formData = utils.merge(formData, body);

View File

@@ -73,7 +73,7 @@ describe('socket.io', () => {
}, (err, res) => {
assert.ifError(err);
helpers.connectSocketIO(res, (err, _io) => {
helpers.connectSocketIO(res, body.csrf_token, (err, _io) => {
io = _io;
assert.ifError(err);
@@ -107,6 +107,14 @@ describe('socket.io', () => {
});
});
it('should return error for invalid eventName type', (done) => {
const eventName = ['topics.loadMoreTags'];
io.emit(eventName, (err) => {
assert.strictEqual(err.message, `[[error:invalid-event, object]]`);
done();
});
});
it('should get installed themes', (done) => {
const themes = ['nodebb-theme-lavender', 'nodebb-theme-persona', 'nodebb-theme-vanilla'];
io.emit('admin.themes.getInstalled', (err, data) => {

View File

@@ -1932,6 +1932,14 @@ describe('Topic\'s', () => {
});
});
it('should only delete one tag from topic', async () => {
const result1 = await topics.post({ uid: adminUid, tags: ['deleteme1', 'deleteme2', 'deleteme3'], title: 'topic tagged with plugins', content: 'topic 1 content', cid: topic.categoryId });
await topics.deleteTag('deleteme2');
const topicData = await topics.getTopicData(result1.topicData.tid);
const tags = topicData.tags.map(t => t.value);
assert.deepStrictEqual(tags, ['deleteme1', 'deleteme3']);
});
it('should delete tag', (done) => {
topics.deleteTag('javascript', (err) => {
assert.ifError(err);

View File

@@ -82,6 +82,7 @@ describe('Topic thumbs', () => {
assert.deepStrictEqual(thumbs, [{
id: topicObj.topicData.tid,
name: 'test.png',
path: `${relativeThumbPaths[0]}`,
url: `${nconf.get('relative_path')}${nconf.get('upload_url')}${relativeThumbPaths[0]}`,
}]);
});
@@ -92,6 +93,7 @@ describe('Topic thumbs', () => {
[{
id: topicObj.topicData.tid,
name: 'test.png',
path: `${relativeThumbPaths[0]}`,
url: `${nconf.get('relative_path')}${nconf.get('upload_url')}${relativeThumbPaths[0]}`,
}],
[],
@@ -200,16 +202,19 @@ describe('Topic thumbs', () => {
{
id: tid,
name: 'test.png',
path: relativeThumbPaths[0],
url: `${nconf.get('relative_path')}${nconf.get('upload_url')}${relativeThumbPaths[0]}`,
},
{
id: tid,
name: 'example.org',
path: 'https://example.org',
url: 'https://example.org',
},
{
id: tid,
name: 'test2.png',
path: relativeThumbPaths[1],
url: `${nconf.get('relative_path')}${nconf.get('upload_url')}${relativeThumbPaths[1]}`,
},
]);

View File

@@ -87,8 +87,7 @@ describe('Upload Controllers', () => {
const oldValue = meta.config.allowedFileExtensions;
meta.config.allowedFileExtensions = 'png,jpg,bmp,html';
require('../src/middleware/uploads').clearCache();
// why / 2? see: helpers.uploadFile for a weird quirk where we actually upload 2 files per upload in our tests.
const times = (meta.config.uploadRateLimitThreshold / 2) + 1;
const times = meta.config.uploadRateLimitThreshold + 1;
async.timesSeries(times, (i, next) => {
helpers.uploadFile(`${nconf.get('url')}/api/post/upload`, path.join(__dirname, '../test/files/503.html'), {}, jar, csrf_token, (err, res, body) => {
if (i + 1 >= times) {
@@ -522,7 +521,7 @@ describe('Upload Controllers', () => {
it('should return files with no post associated with them', async () => {
const orphans = await posts.uploads.getOrphans();
assert.strictEqual(orphans.length, 2);
assert.strictEqual(orphans.length, 1);
orphans.forEach((relPath) => {
assert(relPath.startsWith('files/'));
assert(relPath.endsWith('test.png'));
@@ -553,7 +552,7 @@ describe('Upload Controllers', () => {
await posts.uploads.cleanOrphans();
const orphans = await posts.uploads.getOrphans();
assert.strictEqual(orphans.length, 2);
assert.strictEqual(orphans.length, 1);
});
it('should not touch orphans if they are newer than the configured expiry', async () => {
@@ -561,7 +560,7 @@ describe('Upload Controllers', () => {
await posts.uploads.cleanOrphans();
const orphans = await posts.uploads.getOrphans();
assert.strictEqual(orphans.length, 2);
assert.strictEqual(orphans.length, 1);
});
it('should delete orphans older than the configured number of days', async () => {