mirror of
https://github.com/NodeBB/NodeBB.git
synced 2026-02-10 00:27:35 +01:00
Merge commit '64ac483dddb65ad6cba06a2e2a43a07a361546f1' into v1.15.x
This commit is contained in:
123
CHANGELOG.md
123
CHANGELOG.md
@@ -1,3 +1,126 @@
|
||||
#### v1.15.4 (2020-12-02)
|
||||
|
||||
##### Chores
|
||||
|
||||
* up persona (dde3171c)
|
||||
* fallbacks for nodebb.error (82ca3760)
|
||||
* fallbacks for nodebb.topic (5b269bc5)
|
||||
* remove test code (07fe959c)
|
||||
* incrementing version number - v1.15.3 (d1ae08fa)
|
||||
* update changelog for v1.15.3 (cf157c9b)
|
||||
|
||||
##### New Features
|
||||
|
||||
* #9005, use timestamp in profile/cover images (5f0f476b)
|
||||
* #8983, update pin tooltip in topic (954dc5b7)
|
||||
* option to allow auto-joining of groups (optionally skip the "request membership" step) (685f3c6a)
|
||||
* user notification settings for group.leave event (c1a7968d)
|
||||
* add defaults for composer help (0cba2691)
|
||||
* #8900, postQueue setting for category (1eb5fabd)
|
||||
* #8960, update view count after merge (14bb0a44)
|
||||
* use correct code (557f0f56)
|
||||
* #8989, convert widget nav to dropdown (4c650aee)
|
||||
* add handler for 501 api response (007a3258)
|
||||
* add translation key for pin icon label with expiry (12b3aa0d)
|
||||
* add pinExpiry and pinExpiryISO to topic data (ad8e7700)
|
||||
* add cancel button to pin expiration modal (e1432caf)
|
||||
|
||||
##### Bug Fixes
|
||||
|
||||
* #9032, fix login redirect for sso plugins (6f68f4d2)
|
||||
* #8962, dont show null for purged targets (86b7f8a5)
|
||||
* selector on hooks page (3488daa1)
|
||||
* notification on group.leave incorrectly showing "Guest has left X group" (f7558c60)
|
||||
* #9019, add missing lang strings (b46d2f93)
|
||||
* #9018 (e45b5cba)
|
||||
* #9015, add default value for dailyDigestFreq (0f1fc10f)
|
||||
* spec (cfb7b113)
|
||||
* #8997, don't send notifications if uids already in group (f7c738de)
|
||||
* #9002 ban templates not user friendly (4317cdea)
|
||||
* #9010, show rest of info even if clusterMonitor priv is not granted (202dcef4)
|
||||
* #9007 revoke old sessions after adding (d46740f8)
|
||||
* guests dont always have sid (70073653)
|
||||
* allow guests to see their replies immediately (a4fe4d3c)
|
||||
* privs headers (92d1b8a6)
|
||||
* pwd reset test (f25000cb)
|
||||
* #8991, logout on password reset, dont verify email if password expired (5080f357)
|
||||
* don't show topic search if no search privilege (8adbf54a)
|
||||
* #8998, allow guests to use write api to post/reply (3cd0c9a4)
|
||||
* guest handles to user displayname as well (5a137a0d)
|
||||
* timestamp in queue, add post queue strings (546f58bf)
|
||||
* #8992, set email:confirmed for first admin user (7f5efc3e)
|
||||
* typo in upgrade script, closes #8990 (80f0750b)
|
||||
* #8984, post-queue ux (1269103f)
|
||||
* order (9ab4fb41)
|
||||
* #8982, copy color on tag rename, dont copy if target exists (d3c04afb)
|
||||
* tests (b596e948)
|
||||
* api test (77a6dbac)
|
||||
* remove dupe (cbbda451)
|
||||
* csv test (3de692cd)
|
||||
* spec, remove old tests (4afdf8bc)
|
||||
* #8969, export csv to file (6e6a7a8f)
|
||||
* spec for /tag (88e5cda5)
|
||||
* #8980, fix lang string (f4d217d8)
|
||||
* #8979 (bf171adc)
|
||||
* #8971, disallow flags of privileged users (mods, gmods, admins) (1e7cf1cb)
|
||||
* #8974, with password login for approval queue (dadb2527)
|
||||
* #8974, dont show wrong message on register queue (fdca8b16)
|
||||
* #8973, fix timestamp on ban modal (5c3deb4b)
|
||||
* #8968, don't show topic search if search is not enabled (c8554b78)
|
||||
* flicker on tooltips if server call takes long time (4c7374ea)
|
||||
* missing select/clear all checkbox added to category privileges template (#8967) (a56a6577)
|
||||
* use package.name for theme.id (#8965) (ba3981e2)
|
||||
* winston usages (b8cafefc)
|
||||
* #8957 (414caac0)
|
||||
* dont go back after delete account actions (7e6427bc)
|
||||
* error message (47a19d67)
|
||||
* #8954, clear purged replies and toPids (#8959) (5bb5ec46)
|
||||
* #8955, popstate to purged topic should go to homepage (39dae0aa)
|
||||
* 'already-deleting' error on subsequent account content deletions (21d6225c)
|
||||
* #8949, faster upgrade script (93863bb3)
|
||||
* **deps:**
|
||||
* update dependency nodebb-theme-vanilla to v11.3.8 (#9031) (18707940)
|
||||
* update dependency nodebb-theme-slick to v1.3.5 (#9030) (4085f3e6)
|
||||
* update dependency nodebb-theme-persona to v10.2.98 (#9029) (f7d60c43)
|
||||
* update dependency nodebb-theme-persona to v10.2.97 (42b23a3b)
|
||||
* update dependency nodebb-plugin-composer-default to v6.4.10 (#9025) (43bbfb67)
|
||||
* update dependency nodebb-theme-vanilla to v11.3.7 (#9024) (3f597a55)
|
||||
* update dependency nodebb-plugin-composer-default to v6.4.9 (#9023) (110186b9)
|
||||
* update dependency nodebb-theme-slick to v1.3.4 (#9022) (8dc1437e)
|
||||
* update dependency nodebb-theme-persona to v10.2.96 (#9021) (2c9cd286)
|
||||
* update dependency nodebb-plugin-composer-default to v6.4.8 (#9017) (1f5f2e1d)
|
||||
* update dependency nodebb-plugin-markdown to v8.12.3 (9004319e)
|
||||
* update dependency validator to v13.5.1 (7b39cf4b)
|
||||
* update dependency nodebb-theme-persona to v10.2.95 (#9001) (4ddab380)
|
||||
* update dependency nodebb-theme-persona to v10.2.94 (#9000) (877d8554)
|
||||
* update dependency nodebb-theme-persona to v10.2.93 (#8999) (c44d9d2f)
|
||||
* update dependency nodebb-theme-persona to v10.2.92 (#8995) (346b91eb)
|
||||
* update dependency nodebb-theme-vanilla to v11.3.6 (#8987) (6c980db1)
|
||||
* update dependency nodebb-theme-persona to v10.2.91 (#8986) (8258536a)
|
||||
* update dependency autoprefixer to v10.0.4 (#8985) (fad2d342)
|
||||
* update dependency nodebb-plugin-markdown to v8.12.2 (f5714452)
|
||||
* update dependency nodebb-theme-persona to v10.2.90 (5664807d)
|
||||
* update dependency nodebb-theme-vanilla to v11.3.5 (19fe2493)
|
||||
* update dependency nodebb-theme-persona to v10.2.89 (ad60bc06)
|
||||
* update dependency autoprefixer to v10.0.3 (b2f0d38f)
|
||||
* update dependency benchpressjs to v2.3.0 (6c316be4)
|
||||
* **openapi:**
|
||||
* spec for c1a7968d23f0809e7012edfccf49b193749998ec (69864b87)
|
||||
* spec for 685f3c6aa6173383d6c31b87ed51cf8ed0ca44ce (1bb75e76)
|
||||
* **acp:**
|
||||
* #9008 undefined link for "no users browsing" state on dashboard (54dc449f)
|
||||
* #9009 no-users-browsing untranslated on dashboard (286243cd)
|
||||
* **spec:**
|
||||
* from 6e6a7a8f8a9a75500ba1f336cabc882234212f88 (acb57666)
|
||||
* breaking tests (88a60473)
|
||||
* broken test due to canFlag addition (1b1205a9)
|
||||
|
||||
##### Refactors
|
||||
|
||||
* remove old hack (73746bb4)
|
||||
* add TopicObjectSlim common schema (22715d54)
|
||||
* pin/lock threadTools to use topicCommand, rewrote topicCommand to match categoryCommand signature (15c6f32c)
|
||||
|
||||
#### v1.15.3 (2020-11-26)
|
||||
|
||||
##### Chores
|
||||
|
||||
@@ -100,10 +100,10 @@
|
||||
"nodebb-plugin-markdown": "8.12.3",
|
||||
"nodebb-plugin-mentions": "2.13.5",
|
||||
"nodebb-plugin-soundpack-default": "1.0.0",
|
||||
"nodebb-plugin-spam-be-gone": "0.7.6",
|
||||
"nodebb-plugin-spam-be-gone": "0.7.7",
|
||||
"nodebb-rewards-essentials": "0.1.4",
|
||||
"nodebb-theme-lavender": "5.0.14",
|
||||
"nodebb-theme-persona": "10.2.98",
|
||||
"nodebb-theme-persona": "10.2.99",
|
||||
"nodebb-theme-slick": "1.3.5",
|
||||
"nodebb-theme-vanilla": "11.3.8",
|
||||
"nodebb-widget-essentials": "4.1.2",
|
||||
|
||||
@@ -145,12 +145,18 @@ ajaxify = window.ajaxify || {};
|
||||
app.alertError('[[global:please_log_in]]');
|
||||
app.previousUrl = url;
|
||||
window.location.href = config.relative_path + '/login';
|
||||
} else if ((status === 302 || status === 308) && typeof data.responseJSON === 'string') {
|
||||
ajaxifyTimer = undefined;
|
||||
if (data.responseJSON.startsWith('http://') || data.responseJSON.startsWith('https://')) {
|
||||
window.location.href = data.responseJSON;
|
||||
} else {
|
||||
ajaxify.go(data.responseJSON.slice(1), callback, quiet);
|
||||
} else if (status === 302 || status === 308) {
|
||||
if (data.responseJSON && data.responseJSON.external) {
|
||||
// this is used by sso plugins to redirect to the auth route
|
||||
// cant use ajaxify.go for /auth/sso routes
|
||||
window.location.href = data.responseJSON.external;
|
||||
} else if (typeof data.responseJSON === 'string') {
|
||||
ajaxifyTimer = undefined;
|
||||
if (data.responseJSON.startsWith('http://') || data.responseJSON.startsWith('https://')) {
|
||||
window.location.href = data.responseJSON;
|
||||
} else {
|
||||
ajaxify.go(data.responseJSON.slice(1), callback, quiet);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (textStatus !== 'abort') {
|
||||
@@ -194,8 +200,9 @@ ajaxify = window.ajaxify || {};
|
||||
|
||||
// Allow translation strings in title on ajaxify (#5927)
|
||||
title = translator.unescape(title);
|
||||
|
||||
translator.translate(title, function (translated) {
|
||||
var data = { title: title };
|
||||
$(window).trigger('action:ajaxify.updateTitle', data);
|
||||
translator.translate(data.title, function (translated) {
|
||||
window.document.title = $('<div></div>').html(translated).text();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -106,14 +106,14 @@ define('pictureCropper', ['cropper'], function (Cropper) {
|
||||
return;
|
||||
}
|
||||
|
||||
cropperModal.find('#upload-progress-bar').css('width', '100%');
|
||||
cropperModal.find('#upload-progress-bar').css('width', '0%');
|
||||
cropperModal.find('#upload-progress-box').show().removeClass('hide');
|
||||
|
||||
var socketData = {};
|
||||
socketData[data.paramName] = data.paramValue;
|
||||
socketData.imageData = imageData;
|
||||
|
||||
socket.emit(data.socketMethod, socketData, function (err, imageData) {
|
||||
socketUpload({
|
||||
data: data,
|
||||
imageData: imageData,
|
||||
progressBarEl: cropperModal.find('#upload-progress-bar'),
|
||||
}, function (err, result) {
|
||||
if (err) {
|
||||
cropperModal.find('#upload-progress-box').hide();
|
||||
cropperModal.find('.upload-btn').removeClass('disabled');
|
||||
@@ -121,7 +121,7 @@ define('pictureCropper', ['cropper'], function (Cropper) {
|
||||
return app.alertError(err.message);
|
||||
}
|
||||
|
||||
callback(imageData.url);
|
||||
callback(result.url);
|
||||
cropperModal.modal('hide');
|
||||
});
|
||||
});
|
||||
@@ -143,6 +143,36 @@ define('pictureCropper', ['cropper'], function (Cropper) {
|
||||
});
|
||||
};
|
||||
|
||||
function socketUpload(params, callback) {
|
||||
var socketData = {};
|
||||
socketData[params.data.paramName] = params.data.paramValue;
|
||||
socketData.method = params.data.socketMethod;
|
||||
socketData.size = params.imageData.length;
|
||||
socketData.progress = 0;
|
||||
|
||||
var chunkSize = 100000;
|
||||
function doUpload() {
|
||||
var chunk = params.imageData.slice(socketData.progress, socketData.progress + chunkSize);
|
||||
socket.emit('uploads.upload', {
|
||||
chunk: chunk,
|
||||
params: socketData,
|
||||
}, function (err, result) {
|
||||
if (err) {
|
||||
return app.alertError(err);
|
||||
}
|
||||
|
||||
if (socketData.progress + chunkSize < socketData.size) {
|
||||
socketData.progress += chunk.length;
|
||||
params.progressBarEl.css('width', (socketData.progress / socketData.size * 100).toFixed(2) + '%');
|
||||
return setTimeout(doUpload, 100);
|
||||
}
|
||||
params.progressBarEl.css('width', '100%');
|
||||
callback(null, result);
|
||||
});
|
||||
}
|
||||
doUpload();
|
||||
}
|
||||
|
||||
function checkCORS(cropperTool, data) {
|
||||
var imageData;
|
||||
try {
|
||||
@@ -177,7 +207,7 @@ define('pictureCropper', ['cropper'], function (Cropper) {
|
||||
var file = fileInput[0].files[0];
|
||||
var fileSize = data.hasOwnProperty('fileSize') && data.fileSize !== undefined ? parseInt(data.fileSize, 10) : false;
|
||||
if (fileSize && file.size > fileSize * 1024) {
|
||||
return app.alertError('[[error:file-too-big, ' + fileSize + ']]');
|
||||
return showAlert('error', '[[error:file-too-big, ' + fileSize + ']]');
|
||||
}
|
||||
|
||||
if (file.name.endsWith('.gif')) {
|
||||
|
||||
@@ -145,11 +145,20 @@ helpers.notAllowed = async function (req, res, error) {
|
||||
};
|
||||
|
||||
helpers.redirect = function (res, url, permanent) {
|
||||
if (res.locals.isAPI) {
|
||||
res.set('X-Redirect', encodeURI(url)).status(200).json(encodeURI(url));
|
||||
let redirectUrl;
|
||||
// this is used by sso plugins to redirect to the auth route
|
||||
if (url.hasOwnProperty('external')) {
|
||||
redirectUrl = url.external;
|
||||
url.external = encodeURI(url.external);
|
||||
} else {
|
||||
const redirectUrl = url.startsWith('http://') || url.startsWith('https://') ?
|
||||
url : relative_path + url;
|
||||
redirectUrl = url;
|
||||
url = encodeURI(url);
|
||||
}
|
||||
if (res.locals.isAPI) {
|
||||
res.set('X-Redirect', encodeURI(redirectUrl)).status(200).json(url);
|
||||
} else {
|
||||
redirectUrl = redirectUrl.startsWith('http://') || redirectUrl.startsWith('https://') ?
|
||||
redirectUrl : relative_path + redirectUrl;
|
||||
res.redirect(permanent ? 308 : 307, encodeURI(redirectUrl));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -125,7 +125,7 @@ Controllers.login = async function (req, res) {
|
||||
data.allowLocalLogin = hasLoginPrivilege || parseInt(req.query.local, 10) === 1;
|
||||
|
||||
if (!data.allowLocalLogin && !data.allowRegistration && data.alternate_logins && data.authentication.length === 1) {
|
||||
return helpers.redirect(res, data.authentication[0].url);
|
||||
return helpers.redirect(res, { external: data.authentication[0].url });
|
||||
}
|
||||
|
||||
if (req.loggedIn) {
|
||||
|
||||
@@ -5,12 +5,10 @@
|
||||
* payload and throw an error otherwise.
|
||||
*/
|
||||
|
||||
const fs = require('fs');
|
||||
const fsPromises = fs.promises;
|
||||
const path = require('path');
|
||||
|
||||
const nconf = require('nconf');
|
||||
|
||||
const file = require('../file');
|
||||
const user = require('../user');
|
||||
const groups = require('../groups');
|
||||
const topics = require('../topics');
|
||||
@@ -64,13 +62,12 @@ Assert.path = helpers.try(async (req, res, next) => {
|
||||
const pathToFile = path.join(nconf.get('upload_path'), req.body.path);
|
||||
res.locals.cleanedPath = pathToFile;
|
||||
|
||||
// Guard against path traversal
|
||||
if (!pathToFile.startsWith(nconf.get('upload_path'))) {
|
||||
return controllerHelpers.formatApiResponse(403, res, new Error('[[error:invalid-path]]'));
|
||||
}
|
||||
|
||||
try {
|
||||
await fsPromises.access(pathToFile, fs.constants.F_OK);
|
||||
} catch (e) {
|
||||
if (!await file.exists(pathToFile)) {
|
||||
return controllerHelpers.formatApiResponse(404, res, new Error('[[error:invalid-path]]'));
|
||||
}
|
||||
|
||||
|
||||
@@ -69,6 +69,15 @@ function onConnection(socket) {
|
||||
socket.on('*', function (payload) {
|
||||
onMessage(socket, payload);
|
||||
});
|
||||
|
||||
socket.on('disconnect', function () {
|
||||
onDisconnect(socket);
|
||||
});
|
||||
}
|
||||
|
||||
function onDisconnect(socket) {
|
||||
require('./uploads').clear(socket.id);
|
||||
plugins.hooks.fire('action:sockets.disconnect', { socket: socket });
|
||||
}
|
||||
|
||||
function onConnect(socket) {
|
||||
@@ -82,6 +91,7 @@ function onConnect(socket) {
|
||||
socket.join('sess_' + socket.request.signedCookies[nconf.get('sessionKey')]);
|
||||
Sockets.server.sockets.sockets[socket.id].emit('checkSession', socket.uid);
|
||||
Sockets.server.sockets.sockets[socket.id].emit('setHostname', os.hostname());
|
||||
plugins.hooks.fire('action:sockets.connect', { socket: socket });
|
||||
}
|
||||
|
||||
async function onMessage(socket, payload) {
|
||||
@@ -148,7 +158,8 @@ async function onMessage(socket, payload) {
|
||||
|
||||
function requireModules() {
|
||||
var modules = ['admin', 'categories', 'groups', 'meta', 'modules',
|
||||
'notifications', 'plugins', 'posts', 'topics', 'user', 'blacklist', 'flags',
|
||||
'notifications', 'plugins', 'posts', 'topics', 'user', 'blacklist',
|
||||
'flags', 'uploads',
|
||||
];
|
||||
|
||||
modules.forEach(function (module) {
|
||||
|
||||
52
src/socket.io/uploads.js
Normal file
52
src/socket.io/uploads.js
Normal file
@@ -0,0 +1,52 @@
|
||||
'use strict';
|
||||
|
||||
const socketUser = require('./user');
|
||||
const socketGroup = require('./groups');
|
||||
const image = require('../image');
|
||||
const meta = require('../meta');
|
||||
|
||||
const inProgress = {};
|
||||
|
||||
const uploads = module.exports;
|
||||
|
||||
uploads.upload = async function (socket, data) {
|
||||
const methodToFunc = {
|
||||
'user.uploadCroppedPicture': socketUser.uploadCroppedPicture,
|
||||
'user.updateCover': socketUser.updateCover,
|
||||
'groups.cover.update': socketGroup.cover.update,
|
||||
};
|
||||
if (!socket.uid || !data || !data.chunk || !data.params || !data.params.method || !methodToFunc[data.params.method]) {
|
||||
throw new Error('[[error:invalid-data]]');
|
||||
}
|
||||
|
||||
inProgress[socket.id] = inProgress[socket.id] || {};
|
||||
const socketUploads = inProgress[socket.id];
|
||||
const method = data.params.method;
|
||||
|
||||
socketUploads[method] = socketUploads[method] || { imageData: '' };
|
||||
socketUploads[method].imageData += data.chunk;
|
||||
|
||||
try {
|
||||
const maxSize = data.params.method === 'user.uploadCroppedPicture' ?
|
||||
meta.config.maximumProfileImageSize : meta.config.maximumCoverImageSize;
|
||||
const size = image.sizeFromBase64(socketUploads[method].imageData);
|
||||
|
||||
if (size > maxSize * 1024) {
|
||||
throw new Error('[[error:file-too-big, ' + maxSize + ']]');
|
||||
}
|
||||
if (socketUploads[method].imageData.length < data.params.size) {
|
||||
return;
|
||||
}
|
||||
data.params.imageData = socketUploads[method].imageData;
|
||||
const result = await methodToFunc[data.params.method](socket, data.params);
|
||||
delete socketUploads[method];
|
||||
return result;
|
||||
} catch (err) {
|
||||
delete inProgress[socket.id];
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
|
||||
uploads.clear = function (sid) {
|
||||
delete inProgress[sid];
|
||||
};
|
||||
Reference in New Issue
Block a user