mirror of
https://github.com/NodeBB/NodeBB.git
synced 2025-12-17 14:00:29 +01:00
Compare commits
27 Commits
custom-use
...
v1.19.12
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
12cba2d12c | ||
|
|
bb997a78cc | ||
|
|
354c9c2cc1 | ||
|
|
e9ec1969b5 | ||
|
|
88c9d9d152 | ||
|
|
b450689705 | ||
|
|
81e3c1ba48 | ||
|
|
e0080d9005 | ||
|
|
addd701de2 | ||
|
|
24ba3e84cb | ||
|
|
70a0135209 | ||
|
|
a3ae8c48ce | ||
|
|
e5ca0232de | ||
|
|
8d5ef17248 | ||
|
|
40ce9af189 | ||
|
|
e4bd4f3107 | ||
|
|
4a87b3225c | ||
|
|
673fcfb052 | ||
|
|
3f13a69298 | ||
|
|
b60174f51e | ||
|
|
7388f111b7 | ||
|
|
4bd559deba | ||
|
|
ded19254ac | ||
|
|
5c89557155 | ||
|
|
04ce24e661 | ||
|
|
a24a108a66 | ||
|
|
aa77758afd |
@@ -2,7 +2,7 @@
|
|||||||
"name": "nodebb",
|
"name": "nodebb",
|
||||||
"license": "GPL-3.0",
|
"license": "GPL-3.0",
|
||||||
"description": "NodeBB Forum",
|
"description": "NodeBB Forum",
|
||||||
"version": "1.19.6",
|
"version": "1.19.7",
|
||||||
"homepage": "http://www.nodebb.org",
|
"homepage": "http://www.nodebb.org",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
@@ -54,7 +54,7 @@
|
|||||||
"cookie-parser": "1.4.6",
|
"cookie-parser": "1.4.6",
|
||||||
"cron": "1.8.2",
|
"cron": "1.8.2",
|
||||||
"cropperjs": "1.5.12",
|
"cropperjs": "1.5.12",
|
||||||
"csurf": "1.11.0",
|
"csrf-sync": "4.0.1",
|
||||||
"daemon": "1.1.0",
|
"daemon": "1.1.0",
|
||||||
"diff": "5.0.0",
|
"diff": "5.0.0",
|
||||||
"express": "4.18.0",
|
"express": "4.18.0",
|
||||||
|
|||||||
@@ -3,28 +3,26 @@
|
|||||||
|
|
||||||
define('forum/topic/images', [], function () {
|
define('forum/topic/images', [], function () {
|
||||||
const Images = {};
|
const Images = {};
|
||||||
|
const suffixRegex = /-resized(\.[\w]+)?$/;
|
||||||
|
|
||||||
Images.wrapImagesInLinks = function (posts) {
|
Images.wrapImagesInLinks = function (posts) {
|
||||||
posts.find('[component="post/content"] img:not(.emoji)').each(function () {
|
posts.find('[component="post/content"] img:not(.emoji)').each(function () {
|
||||||
const $this = $(this);
|
const $this = $(this);
|
||||||
let src = $this.attr('src') || '';
|
let src = $this.attr('src') || '';
|
||||||
const alt = $this.attr('alt') || '';
|
|
||||||
const suffixRegex = /-resized(\.[\w]+)?$/;
|
|
||||||
|
|
||||||
if (src === 'about:blank') {
|
if (src === 'about:blank') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!$this.parent().is('a')) {
|
||||||
if (utils.isRelativeUrl(src) && suffixRegex.test(src)) {
|
if (utils.isRelativeUrl(src) && suffixRegex.test(src)) {
|
||||||
src = src.replace(suffixRegex, '$1');
|
src = src.replace(suffixRegex, '$1');
|
||||||
}
|
}
|
||||||
|
const alt = $this.attr('alt') || '';
|
||||||
const srcExt = src.split('.').slice(1).pop();
|
const srcExt = src.split('.').slice(1).pop();
|
||||||
const altFilename = alt.split('/').pop();
|
const altFilename = alt.split('/').pop();
|
||||||
const altExt = altFilename.split('.').slice(1).pop();
|
const altExt = altFilename.split('.').slice(1).pop();
|
||||||
|
|
||||||
if (!$this.parent().is('a')) {
|
|
||||||
$this.wrap('<a href="' + src + '" ' +
|
$this.wrap('<a href="' + src + '" ' +
|
||||||
(!srcExt && altExt ? ' download="' + altFilename + '" ' : '') +
|
(!srcExt && altExt ? ' download="' + utils.escapeHTML(altFilename) + '" ' : '') +
|
||||||
' target="_blank" rel="noopener">');
|
' target="_blank" rel="noopener">');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -12,6 +12,9 @@ socket = window.socket;
|
|||||||
reconnectionDelay: config.reconnectionDelay,
|
reconnectionDelay: config.reconnectionDelay,
|
||||||
transports: config.socketioTransports,
|
transports: config.socketioTransports,
|
||||||
path: config.relative_path + '/socket.io',
|
path: config.relative_path + '/socket.io',
|
||||||
|
query: {
|
||||||
|
_csrf: config.csrf_token,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
socket = io(config.websocketAddress, ioParams);
|
socket = io(config.websocketAddress, ioParams);
|
||||||
|
|||||||
@@ -290,13 +290,11 @@
|
|||||||
|
|
||||||
const utils = {
|
const utils = {
|
||||||
generateUUID: function () {
|
generateUUID: function () {
|
||||||
/* eslint-disable no-bitwise */
|
// from https://github.com/tracker1/node-uuid4/blob/master/browser.js
|
||||||
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
|
const temp_url = URL.createObjectURL(new Blob());
|
||||||
const r = Math.random() * 16 | 0;
|
const uuid = temp_url.toString();
|
||||||
const v = c === 'x' ? r : ((r & 0x3) | 0x8);
|
URL.revokeObjectURL(temp_url);
|
||||||
return v.toString(16);
|
return uuid.split(/[:\/]/g).pop().toLowerCase(); // remove prefixes
|
||||||
});
|
|
||||||
/* eslint-enable no-bitwise */
|
|
||||||
},
|
},
|
||||||
// https://github.com/substack/node-ent/blob/master/index.js
|
// https://github.com/substack/node-ent/blob/master/index.js
|
||||||
decodeHTMLEntities: function (html) {
|
decodeHTMLEntities: function (html) {
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ const categories = require('../categories');
|
|||||||
const plugins = require('../plugins');
|
const plugins = require('../plugins');
|
||||||
const translator = require('../translator');
|
const translator = require('../translator');
|
||||||
const languages = require('../languages');
|
const languages = require('../languages');
|
||||||
|
const { generateToken } = require('../middleware/csrf');
|
||||||
|
|
||||||
const apiController = module.exports;
|
const apiController = module.exports;
|
||||||
|
|
||||||
@@ -64,7 +65,7 @@ apiController.loadConfig = async function (req) {
|
|||||||
'cache-buster': meta.config['cache-buster'] || '',
|
'cache-buster': meta.config['cache-buster'] || '',
|
||||||
topicPostSort: meta.config.topicPostSort || 'oldest_to_newest',
|
topicPostSort: meta.config.topicPostSort || 'oldest_to_newest',
|
||||||
categoryTopicSort: meta.config.categoryTopicSort || 'newest_to_oldest',
|
categoryTopicSort: meta.config.categoryTopicSort || 'newest_to_oldest',
|
||||||
csrf_token: req.uid >= 0 && req.csrfToken && req.csrfToken(),
|
csrf_token: req.uid >= 0 ? generateToken(req) : false,
|
||||||
searchEnabled: plugins.hooks.hasListeners('filter:search.query'),
|
searchEnabled: plugins.hooks.hasListeners('filter:search.query'),
|
||||||
searchDefaultInQuick: meta.config.searchDefaultInQuick || 'titles',
|
searchDefaultInQuick: meta.config.searchDefaultInQuick || 'titles',
|
||||||
bootswatchSkin: meta.config.bootswatchSkin || '',
|
bootswatchSkin: meta.config.bootswatchSkin || '',
|
||||||
|
|||||||
28
src/middleware/csrf.js
Normal file
28
src/middleware/csrf.js
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
'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.body && req.body._csrf) {
|
||||||
|
return req.body._csrf;
|
||||||
|
} else if (req.query && req.query._csrf) {
|
||||||
|
return req.query._csrf;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
size: 64,
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
generateToken,
|
||||||
|
csrfSynchronisedProtection,
|
||||||
|
isRequestValid,
|
||||||
|
};
|
||||||
@@ -2,13 +2,14 @@
|
|||||||
|
|
||||||
const async = require('async');
|
const async = require('async');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const csrf = require('csurf');
|
|
||||||
const validator = require('validator');
|
const validator = require('validator');
|
||||||
const nconf = require('nconf');
|
const nconf = require('nconf');
|
||||||
const toobusy = require('toobusy-js');
|
const toobusy = require('toobusy-js');
|
||||||
const LRU = require('lru-cache');
|
const LRU = require('lru-cache');
|
||||||
const util = require('util');
|
const util = require('util');
|
||||||
|
|
||||||
|
const { csrfSynchronisedProtection } = require('./csrf');
|
||||||
|
|
||||||
const plugins = require('../plugins');
|
const plugins = require('../plugins');
|
||||||
const meta = require('../meta');
|
const meta = require('../meta');
|
||||||
const user = require('../user');
|
const user = require('../user');
|
||||||
@@ -34,7 +35,7 @@ middleware.regexes = {
|
|||||||
timestampedUpload: /^\d+-.+$/,
|
timestampedUpload: /^\d+-.+$/,
|
||||||
};
|
};
|
||||||
|
|
||||||
const csrfMiddleware = csrf();
|
const csrfMiddleware = csrfSynchronisedProtection;
|
||||||
|
|
||||||
middleware.applyCSRF = function (req, res, next) {
|
middleware.applyCSRF = function (req, res, next) {
|
||||||
if (req.uid >= 0) {
|
if (req.uid >= 0) {
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ const meta = require('../meta');
|
|||||||
const controllers = require('../controllers');
|
const controllers = require('../controllers');
|
||||||
const helpers = require('../controllers/helpers');
|
const helpers = require('../controllers/helpers');
|
||||||
const plugins = require('../plugins');
|
const plugins = require('../plugins');
|
||||||
|
const { generateToken } = require('../middleware/csrf');
|
||||||
|
|
||||||
let loginStrategies = [];
|
let loginStrategies = [];
|
||||||
|
|
||||||
@@ -94,7 +95,7 @@ Auth.reloadRoutes = async function (params) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (strategy.checkState !== false) {
|
if (strategy.checkState !== false) {
|
||||||
req.session.ssoState = req.csrfToken && req.csrfToken();
|
req.session.ssoState = generateToken(req, true);
|
||||||
opts.state = req.session.ssoState;
|
opts.state = req.session.ssoState;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ const logger = require('../logger');
|
|||||||
const plugins = require('../plugins');
|
const plugins = require('../plugins');
|
||||||
const ratelimit = require('../middleware/ratelimit');
|
const ratelimit = require('../middleware/ratelimit');
|
||||||
|
|
||||||
const Namespaces = {};
|
const Namespaces = Object.create(null);
|
||||||
|
|
||||||
const Sockets = module.exports;
|
const Sockets = module.exports;
|
||||||
|
|
||||||
@@ -34,13 +34,25 @@ Sockets.init = async function (server) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
io.use(authorize);
|
|
||||||
|
|
||||||
io.on('connection', onConnection);
|
io.on('connection', onConnection);
|
||||||
|
|
||||||
const opts = {
|
const opts = {
|
||||||
transports: nconf.get('socket.io:transports') || ['polling', 'websocket'],
|
transports: nconf.get('socket.io:transports') || ['polling', 'websocket'],
|
||||||
cookie: false,
|
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.
|
* 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) {
|
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;
|
socket.request.ip = socket.ip;
|
||||||
logger.io_one(socket, socket.uid);
|
logger.io_one(socket, socket.uid);
|
||||||
|
|
||||||
@@ -123,7 +139,7 @@ async function onMessage(socket, payload) {
|
|||||||
const parts = eventName.toString().split('.');
|
const parts = eventName.toString().split('.');
|
||||||
const namespace = parts[0];
|
const namespace = parts[0];
|
||||||
const methodToCall = parts.reduce((prev, cur) => {
|
const methodToCall = parts.reduce((prev, cur) => {
|
||||||
if (prev !== null && prev[cur]) {
|
if (prev !== null && prev[cur] && (!prev.hasOwnProperty || prev.hasOwnProperty(cur))) {
|
||||||
return prev[cur];
|
return prev[cur];
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
@@ -225,9 +241,7 @@ async function validateSession(socket, errorMsg) {
|
|||||||
|
|
||||||
const cookieParserAsync = util.promisify((req, callback) => cookieParser(req, {}, err => callback(err)));
|
const cookieParserAsync = util.promisify((req, callback) => cookieParser(req, {}, err => callback(err)));
|
||||||
|
|
||||||
async function authorize(socket, callback) {
|
async function authorize(request, callback) {
|
||||||
const { request } = socket;
|
|
||||||
|
|
||||||
if (!request) {
|
if (!request) {
|
||||||
return callback(new Error('[[error:not-authorized]]'));
|
return callback(new Error('[[error:not-authorized]]'));
|
||||||
}
|
}
|
||||||
@@ -240,15 +254,13 @@ async function authorize(socket, callback) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const sessionData = await getSessionAsync(sessionId);
|
const sessionData = await getSessionAsync(sessionId);
|
||||||
|
|
||||||
if (sessionData && sessionData.passport && sessionData.passport.user) {
|
|
||||||
request.session = sessionData;
|
request.session = sessionData;
|
||||||
socket.uid = parseInt(sessionData.passport.user, 10);
|
let uid = 0;
|
||||||
} else {
|
if (sessionData && sessionData.passport && sessionData.passport.user) {
|
||||||
socket.uid = 0;
|
uid = parseInt(sessionData.passport.user, 10);
|
||||||
}
|
}
|
||||||
request.uid = socket.uid;
|
request.uid = uid;
|
||||||
callback();
|
callback(null, uid);
|
||||||
}
|
}
|
||||||
|
|
||||||
Sockets.in = function (room) {
|
Sockets.in = function (room) {
|
||||||
|
|||||||
@@ -64,6 +64,10 @@ module.exports = function (SocketUser) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
async function doExport(socket, data, type) {
|
async function doExport(socket, data, type) {
|
||||||
|
const validTypes = ['profile', 'posts', 'uploads'];
|
||||||
|
if (!validTypes.includes(type)) {
|
||||||
|
throw new Error('[[error:invalid-data]]');
|
||||||
|
}
|
||||||
if (!socket.uid) {
|
if (!socket.uid) {
|
||||||
throw new Error('[[error:invalid-uid]]');
|
throw new Error('[[error:invalid-uid]]');
|
||||||
}
|
}
|
||||||
|
|||||||
14
src/utils.js
14
src/utils.js
@@ -1,3 +1,17 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
const crypto = require('crypto');
|
||||||
|
|
||||||
module.exports = require('../public/src/utils');
|
module.exports = require('../public/src/utils');
|
||||||
|
|
||||||
|
module.exports.generateUUID = function () {
|
||||||
|
// from https://github.com/tracker1/node-uuid4/blob/master/index.js
|
||||||
|
let rnd = crypto.randomBytes(16);
|
||||||
|
/* eslint-disable no-bitwise */
|
||||||
|
rnd[6] = (rnd[6] & 0x0f) | 0x40;
|
||||||
|
rnd[8] = (rnd[8] & 0x3f) | 0x80;
|
||||||
|
/* eslint-enable no-bitwise */
|
||||||
|
rnd = rnd.toString('hex').match(/(.{8})(.{4})(.{4})(.{4})(.{12})/);
|
||||||
|
rnd.shift();
|
||||||
|
return rnd.join('-');
|
||||||
|
};
|
||||||
@@ -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');
|
const io = require('socket.io-client');
|
||||||
let cookies = res.headers['set-cookie'];
|
let cookies = res.headers['set-cookie'];
|
||||||
cookies = cookies.filter(c => /express.sid=[^;]+;/.test(c));
|
cookies = cookies.filter(c => /express.sid=[^;]+;/.test(c));
|
||||||
@@ -106,6 +106,9 @@ helpers.connectSocketIO = function (res, callback) {
|
|||||||
Origin: nconf.get('url'),
|
Origin: nconf.get('url'),
|
||||||
Cookie: cookie,
|
Cookie: cookie,
|
||||||
},
|
},
|
||||||
|
query: {
|
||||||
|
_csrf: csrf_token,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on('connect', () => {
|
socket.on('connect', () => {
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ describe('socket.io', () => {
|
|||||||
}, (err, res) => {
|
}, (err, res) => {
|
||||||
assert.ifError(err);
|
assert.ifError(err);
|
||||||
|
|
||||||
helpers.connectSocketIO(res, (err, _io) => {
|
helpers.connectSocketIO(res, body.csrf_token, (err, _io) => {
|
||||||
io = _io;
|
io = _io;
|
||||||
assert.ifError(err);
|
assert.ifError(err);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user