Compare commits

...

87 Commits

Author SHA1 Message Date
Barış Soner Uşaklı
624738359a chore: up version 2025-06-16 12:59:05 -04:00
Barış Soner Uşaklı
15c3abb6d5 fix: add sanitizesvg 2025-06-16 12:58:51 -04:00
Barış Soner Uşaklı
7ead74fe9b fix: sanitize svg when uploading site-logo, default avatar and og:image 2025-06-16 12:57:30 -04:00
Barış Soner Uşaklı
cbbdc79357 chore: up version 2025-06-09 11:21:26 -04:00
Barış Soner Uşaklı
e0d5f92b2a fix: escape, query params 2025-06-09 11:19:45 -04:00
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
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
df08b47163 Merge commit 'c27567289f9937abd4abe6960a9b6e387cf68331' into v2.x 2023-04-11 01:49:09 +00: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
73ff25887c Merge commit '830f142b7aea2e597294a84d52c05aab3a3539ca' into v2.x 2023-03-27 15:12:54 +00: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
bb725987b3 Merge commit '73a50d17180dcd6cb42ef9cf305a480f92b4af05' into v2.x 2023-03-19 16:31:24 +00: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
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
22 changed files with 184 additions and 44 deletions

View File

@@ -2,7 +2,7 @@
"name": "nodebb",
"license": "GPL-3.0",
"description": "NodeBB Forum",
"version": "2.8.11",
"version": "2.8.20",
"homepage": "http://www.nodebb.org",
"repository": {
"type": "git",
@@ -55,7 +55,7 @@
"cookie-parser": "1.4.6",
"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.5",
"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

@@ -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

@@ -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

@@ -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

@@ -1,5 +1,6 @@
'use strict';
const validator = require('validator');
const db = require('../../database');
const events = require('../../events');
const pagination = require('../../pagination');
@@ -39,6 +40,12 @@ eventsController.get = async function (req, res) {
events: eventData,
pagination: pagination.create(page, pageCount, req.query),
types: types,
query: req.query,
query: {
start: validator.escape(String(req.query.start)),
end: validator.escape(String(req.query.end)),
username: validator.escape(String(req.query.username)),
group: validator.escape(String(req.query.group)),
perPage: validator.escape(String(req.query.perPage)),
},
});
};

View File

@@ -119,11 +119,49 @@ uploadsController.uploadCategoryPicture = async function (req, res, next) {
}
if (validateUpload(res, uploadedFile, allowedImageTypes)) {
if (uploadedFile.path.endsWith('.svg')) {
await sanitizeSvg(uploadedFile.path);
}
const filename = `category-${params.cid}${path.extname(uploadedFile.name)}`;
await uploadImage(filename, 'category', uploadedFile, req, res, next);
}
};
async function sanitizeSvg(filePath) {
const dirty = await fs.promises.readFile(filePath, 'utf8');
const clean = sanitizeHtml(dirty, {
allowedTags: [
'svg', 'g', 'defs', 'linearGradient', 'radialGradient', 'stop',
'circle', 'ellipse', 'polygon', 'polyline', 'path', 'rect',
'line', 'text', 'tspan', 'use', 'symbol', 'clipPath', 'mask', 'pattern',
'filter', 'feGaussianBlur', 'feOffset', 'feBlend', 'feColorMatrix', 'feMerge', 'feMergeNode',
],
allowedAttributes: {
'*': [
// Geometry
'x', 'y', 'x1', 'x2', 'y1', 'y2', 'cx', 'cy', 'r', 'rx', 'ry',
'width', 'height', 'd', 'points', 'viewBox', 'transform',
// Presentation
'fill', 'stroke', 'stroke-width', 'opacity',
'stop-color', 'stop-opacity', 'offset', 'style', 'class',
// Text
'text-anchor', 'font-size', 'font-family',
// Misc
'id', 'clip-path', 'mask', 'filter', 'gradientUnits', 'gradientTransform',
'xmlns', 'preserveAspectRatio',
],
},
parser: {
lowerCaseTags: false,
lowerCaseAttributeNames: false,
},
});
await fs.promises.writeFile(filePath, clean);
}
uploadsController.uploadFavicon = async function (req, res, next) {
const uploadedFile = req.files.files[0];
const allowedTypes = ['image/x-icon', 'image/vnd.microsoft.icon'];
@@ -184,10 +222,6 @@ uploadsController.uploadMaskableIcon = async function (req, res, next) {
}
};
uploadsController.uploadLogo = async function (req, res, next) {
await upload('site-logo', req, res, next);
};
uploadsController.uploadFile = async function (req, res, next) {
const uploadedFile = req.files.files[0];
let params;
@@ -208,6 +242,10 @@ uploadsController.uploadFile = async function (req, res, next) {
}
};
uploadsController.uploadLogo = async function (req, res, next) {
await upload('site-logo', req, res, next);
};
uploadsController.uploadDefaultAvatar = async function (req, res, next) {
await upload('avatar-default', req, res, next);
};
@@ -220,6 +258,9 @@ async function upload(name, req, res, next) {
const uploadedFile = req.files.files[0];
if (validateUpload(res, uploadedFile, allowedImageTypes)) {
if (uploadedFile.path.endsWith('.svg')) {
await sanitizeSvg(uploadedFile.path);
}
const filename = name + path.extname(uploadedFile.name);
await uploadImage(filename, 'system', uploadedFile, req, res, next);
}

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

@@ -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

@@ -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);

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) {

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

@@ -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);
@@ -231,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]]'));
}
@@ -246,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

@@ -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

@@ -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()', () => {

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', () => {

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);