mirror of
https://github.com/NodeBB/NodeBB.git
synced 2025-12-24 09:20:32 +01:00
Compare commits
12 Commits
v1.17.2-be
...
v1.17.x
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a07df6d0df | ||
|
|
67685ab637 | ||
|
|
d390755801 | ||
|
|
bad359f308 | ||
|
|
51b3bb4481 | ||
|
|
00c6ddfdaf | ||
|
|
e4344d31d9 | ||
|
|
1fad6f6804 | ||
|
|
a07b898864 | ||
|
|
d1a7a71003 | ||
|
|
e92f5fc561 | ||
|
|
3675d343ef |
@@ -1,7 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
|
||||
define('forum/login', ['jquery-form'], function () {
|
||||
define('forum/login', ['translator', 'jquery-form'], function (translator) {
|
||||
var Login = {
|
||||
_capsState: false,
|
||||
};
|
||||
@@ -44,17 +44,22 @@ define('forum/login', ['jquery-form'], function () {
|
||||
window.location.href = pathname + '?' + qs;
|
||||
},
|
||||
error: function (data) {
|
||||
var message = data.responseText;
|
||||
var errInfo = data.responseJSON;
|
||||
if (data.status === 403 && data.responseText === 'Forbidden') {
|
||||
window.location.href = config.relative_path + '/login?error=csrf-invalid';
|
||||
} else {
|
||||
errorEl.find('p').translateText(data.responseText);
|
||||
errorEl.show();
|
||||
submitEl.removeClass('disabled');
|
||||
} else if (errInfo && errInfo.hasOwnProperty('banned_until')) {
|
||||
message = errInfo.banned_until ?
|
||||
translator.compile('error:user-banned-reason-until', (new Date(errInfo.banned_until).toLocaleString()), errInfo.reason) :
|
||||
'[[error:user-banned-reason, ' + errInfo.reason + ']]';
|
||||
}
|
||||
errorEl.find('p').translateText(message);
|
||||
errorEl.show();
|
||||
submitEl.removeClass('disabled');
|
||||
|
||||
// Select the entire password if that field has focus
|
||||
if ($('#password:focus').length) {
|
||||
$('#password').select();
|
||||
}
|
||||
// Select the entire password if that field has focus
|
||||
if ($('#password:focus').length) {
|
||||
$('#password').select();
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@@ -15,7 +15,6 @@ const user = require('../user');
|
||||
const plugins = require('../plugins');
|
||||
const utils = require('../utils');
|
||||
const slugify = require('../slugify');
|
||||
const translator = require('../translator');
|
||||
const helpers = require('./helpers');
|
||||
const privileges = require('../privileges');
|
||||
const sockets = require('../socket.io');
|
||||
@@ -263,7 +262,7 @@ function continueLogin(strategy, req, res, next) {
|
||||
passport.authenticate(strategy, async (err, userData, info) => {
|
||||
if (err) {
|
||||
plugins.hooks.fire('action:login.continue', { req, strategy, userData, error: err });
|
||||
return helpers.noScriptErrors(req, res, err.message, 403);
|
||||
return helpers.noScriptErrors(req, res, err.data || err.message, 403);
|
||||
}
|
||||
|
||||
if (!userData) {
|
||||
@@ -423,8 +422,7 @@ authenticationController.localLogin = async function (req, username, password, n
|
||||
userData.isAdminOrGlobalMod = isAdminOrGlobalMod;
|
||||
|
||||
if (!canLoginIfBanned) {
|
||||
const banMesage = await getBanInfo(uid);
|
||||
return next(new Error(banMesage));
|
||||
return next(await getBanError(uid));
|
||||
}
|
||||
|
||||
// Doing this after the ban check, because user's privileges might change after a ban expires
|
||||
@@ -483,19 +481,19 @@ authenticationController.logout = async function (req, res, next) {
|
||||
}
|
||||
};
|
||||
|
||||
async function getBanInfo(uid) {
|
||||
async function getBanError(uid) {
|
||||
try {
|
||||
const banInfo = await user.getLatestBanInfo(uid);
|
||||
|
||||
if (!banInfo.reason) {
|
||||
banInfo.reason = await translator.translate('[[user:info.banned-no-reason]]');
|
||||
banInfo.reason = '[[user:info.banned-no-reason]]';
|
||||
}
|
||||
return banInfo.banned_until ?
|
||||
`[[error:user-banned-reason-until, ${banInfo.banned_until_readable}, ${banInfo.reason}]]` :
|
||||
`[[error:user-banned-reason, ${banInfo.reason}]]`;
|
||||
const err = new Error(banInfo.reason);
|
||||
err.data = banInfo;
|
||||
return err;
|
||||
} catch (err) {
|
||||
if (err.message === 'no-ban-info') {
|
||||
return '[[error:user-banned]]';
|
||||
return new Error('[[error:user-banned]]');
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
|
||||
@@ -20,7 +20,10 @@ const url = nconf.get('url');
|
||||
|
||||
helpers.noScriptErrors = async function (req, res, error, httpStatus) {
|
||||
if (req.body.noscript !== 'true') {
|
||||
return res.status(httpStatus).send(error);
|
||||
if (typeof error === 'string') {
|
||||
return res.status(httpStatus).send(error);
|
||||
}
|
||||
return res.status(httpStatus).json(error);
|
||||
}
|
||||
const middleware = require('../middleware');
|
||||
const httpStatusString = httpStatus.toString();
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
const nconf = require('nconf');
|
||||
const qs = require('querystring');
|
||||
|
||||
const user = require('../user');
|
||||
const meta = require('../meta');
|
||||
@@ -58,7 +59,7 @@ topicsController.get = async function getTopic(req, res, callback) {
|
||||
}
|
||||
|
||||
if (!res.locals.isAPI && (!req.params.slug || topicData.slug !== `${tid}/${req.params.slug}`) && (topicData.slug && topicData.slug !== `${tid}/`)) {
|
||||
return helpers.redirect(res, `/topic/${topicData.slug}${postIndex ? `/${postIndex}` : ''}${currentPage > 1 ? `?page=${currentPage}` : ''}`, true);
|
||||
return helpers.redirect(res, `/topic/${topicData.slug}${postIndex ? `/${postIndex}` : ''}${generateQueryString(req.query)}`, true);
|
||||
}
|
||||
|
||||
if (postIndex === 'unread') {
|
||||
@@ -66,7 +67,7 @@ topicsController.get = async function getTopic(req, res, callback) {
|
||||
}
|
||||
|
||||
if (utils.isNumber(postIndex) && topicData.postcount > 0 && (postIndex < 1 || postIndex > topicData.postcount)) {
|
||||
return helpers.redirect(res, `/topic/${req.params.topic_id}/${req.params.slug}${postIndex > topicData.postcount ? `/${topicData.postcount}` : ''}`);
|
||||
return helpers.redirect(res, `/topic/${tid}/${req.params.slug}${postIndex > topicData.postcount ? `/${topicData.postcount}` : ''}${generateQueryString(req.query)}`);
|
||||
}
|
||||
postIndex = Math.max(1, postIndex);
|
||||
const sort = req.query.sort || settings.topicPostSort;
|
||||
@@ -120,6 +121,11 @@ topicsController.get = async function getTopic(req, res, callback) {
|
||||
res.render('topic', topicData);
|
||||
};
|
||||
|
||||
function generateQueryString(query) {
|
||||
const qString = qs.stringify(query);
|
||||
return qString.length ? `?${qString}` : '';
|
||||
}
|
||||
|
||||
function calculatePageFromIndex(postIndex, settings) {
|
||||
return 1 + Math.floor((postIndex - 1) / settings.postsPerPage);
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ connection.getConnectionString = function (mongo) {
|
||||
let usernamePassword = '';
|
||||
const uri = mongo.uri || '';
|
||||
if (mongo.username && mongo.password) {
|
||||
usernamePassword = `${nconf.get('mongo:username')}:${encodeURIComponent(nconf.get('mongo:password'))}@`;
|
||||
usernamePassword = `${mongo.username}:${encodeURIComponent(mongo.password)}@`;
|
||||
} else if (!uri.includes('@') || !uri.slice(uri.indexOf('://') + 3, uri.indexOf('@'))) {
|
||||
winston.warn('You have no mongo username/password setup!');
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ module.exports = function (module) {
|
||||
return;
|
||||
}
|
||||
const bulk = module.client.collection('objects').initializeUnorderedBulkOp();
|
||||
data.forEach(item => bulk.find({ _key: item[0], value: String(item[1]) }).remove());
|
||||
data.forEach(item => bulk.find({ _key: item[0], value: String(item[1]) }).delete());
|
||||
await bulk.execute();
|
||||
};
|
||||
};
|
||||
|
||||
@@ -13,7 +13,11 @@ const files = fs.readdirSync(path.join(paths.nodeModules, '/timeago/locales'));
|
||||
Languages.timeagoCodes = files.filter(f => f.startsWith('jquery.timeago')).map(f => f.split('.')[2]);
|
||||
|
||||
Languages.get = async function (language, namespace) {
|
||||
const data = await fs.promises.readFile(path.join(languagesPath, language, `${namespace}.json`), 'utf8');
|
||||
const pathToLanguageFile = path.join(languagesPath, language, `${namespace}.json`);
|
||||
if (!pathToLanguageFile.startsWith(languagesPath)) {
|
||||
throw new Error('[[error:invalid-path]]');
|
||||
}
|
||||
const data = await fs.promises.readFile(pathToLanguageFile, 'utf8');
|
||||
const parsed = JSON.parse(data) || {};
|
||||
const result = await plugins.hooks.fire('filter:languages.get', {
|
||||
language,
|
||||
|
||||
@@ -51,13 +51,9 @@ Auth.getLoginStrategies = function () {
|
||||
};
|
||||
|
||||
Auth.verifyToken = async function (token, done) {
|
||||
let { tokens = [] } = await meta.settings.get('core.api');
|
||||
tokens = tokens.reduce((memo, cur) => {
|
||||
memo[cur.token] = cur.uid;
|
||||
return memo;
|
||||
}, {});
|
||||
|
||||
const uid = tokens[token];
|
||||
const { tokens = [] } = await meta.settings.get('core.api');
|
||||
const tokenObj = tokens.find(t => t.token === token);
|
||||
const uid = tokenObj ? tokenObj.uid : undefined;
|
||||
|
||||
if (uid !== undefined) {
|
||||
if (parseInt(uid, 10) > 0) {
|
||||
|
||||
@@ -15,11 +15,12 @@ uploads.upload = async function (socket, data) {
|
||||
'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]) {
|
||||
if (!socket.uid || !data || !data.chunk ||
|
||||
!data.params || !data.params.method || !methodToFunc.hasOwnProperty(data.params.method)) {
|
||||
throw new Error('[[error:invalid-data]]');
|
||||
}
|
||||
|
||||
inProgress[socket.id] = inProgress[socket.id] || {};
|
||||
inProgress[socket.id] = inProgress[socket.id] || Object.create(null);
|
||||
const socketUploads = inProgress[socket.id];
|
||||
const { method } = data.params;
|
||||
|
||||
|
||||
@@ -105,7 +105,7 @@ module.exports = function (User) {
|
||||
throw new Error('[[error:no-user]]');
|
||||
}
|
||||
|
||||
await plugins.hooks.fire('static:user.delete', { uid: uid });
|
||||
await plugins.hooks.fire('static:user.delete', { uid: uid, userData: userData });
|
||||
await deleteVotes(uid);
|
||||
await deleteChats(uid);
|
||||
await User.auth.revokeAllSessions(uid);
|
||||
|
||||
@@ -487,7 +487,15 @@ describe('authentication', () => {
|
||||
loginUser(bannedUser.username, bannedUser.pw, (err, res, body) => {
|
||||
assert.ifError(err);
|
||||
assert.equal(res.statusCode, 403);
|
||||
assert.equal(body, '[[error:user-banned-reason, spammer]]');
|
||||
delete body.timestamp;
|
||||
assert.deepStrictEqual(body, {
|
||||
banned_until: 0,
|
||||
banned_until_readable: '',
|
||||
expiry: 0,
|
||||
expiry_readable: '',
|
||||
reason: 'spammer',
|
||||
uid: 6,
|
||||
});
|
||||
user.bans.unban(bannedUser.uid, (err) => {
|
||||
assert.ifError(err);
|
||||
const expiry = Date.now() + 10000;
|
||||
@@ -496,7 +504,8 @@ describe('authentication', () => {
|
||||
loginUser(bannedUser.username, bannedUser.pw, (err, res, body) => {
|
||||
assert.ifError(err);
|
||||
assert.equal(res.statusCode, 403);
|
||||
assert.equal(body, `[[error:user-banned-reason-until, ${utils.toISOString(expiry)}, No reason given.]]`);
|
||||
assert(body.banned_until);
|
||||
assert(body.reason, '[[user:info.banned-no-reason]]');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
|
||||
const assert = require('assert');
|
||||
const assert = require('assert');
|
||||
const async = require('async');
|
||||
const request = require('request');
|
||||
const nconf = require('nconf');
|
||||
|
||||
@@ -34,6 +34,11 @@ describe('Translator shim', () => {
|
||||
const translated = await shim.translate('', 'en-GB');
|
||||
assert.strictEqual(translated, '');
|
||||
});
|
||||
|
||||
it('should not allow path traversal', async () => {
|
||||
const t = await shim.translate('[[../../../../config:secret]]');
|
||||
assert.strictEqual(t, 'secret');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user