This commit is contained in:
Barış Soner Uşaklı
2017-05-08 13:49:35 -04:00
481 changed files with 1430 additions and 654 deletions

View File

@@ -9,7 +9,7 @@ process.on('message', function (msg) {
if (msg.type === 'hash') {
hashPassword(msg.password, msg.rounds);
} else if (msg.type === 'compare') {
bcrypt.compare(msg.password, msg.hash, done);
bcrypt.compare(String(msg.password || ''), String(msg.hash || ''), done);
}
});

View File

@@ -58,7 +58,7 @@ module.exports = function (Categories) {
], next);
},
function (next) {
async.each(privileges.privilegeList, function (privilege, next) {
async.eachSeries(privileges.privilegeList, function (privilege, next) {
groups.destroy('cid:' + cid + ':privileges:' + privilege, next);
}, next);
},

View File

@@ -16,22 +16,30 @@ var info = {};
infoController.get = function (req, res) {
info = {};
pubsub.publish('sync:node:info:start');
var timeoutMS = 1000;
setTimeout(function () {
var data = [];
Object.keys(info).forEach(function (key) {
data.push(info[key]);
});
data.sort(function (a, b) {
if (a.os.hostname < b.os.hostname) {
if (a.id < b.id) {
return -1;
}
if (a.os.hostname > b.os.hostname) {
if (a.id > b.id) {
return 1;
}
return 0;
});
res.render('admin/development/info', { info: data, infoJSON: JSON.stringify(data, null, 4), host: os.hostname(), port: nconf.get('port') });
}, 500);
res.render('admin/development/info', {
info: data,
infoJSON: JSON.stringify(data, null, 4),
host: os.hostname(),
port: nconf.get('port'),
nodeCount: data.length,
timeout: timeoutMS,
});
}, timeoutMS);
};
pubsub.on('sync:node:info:start', function () {
@@ -39,7 +47,8 @@ pubsub.on('sync:node:info:start', function () {
if (err) {
return winston.error(err);
}
pubsub.publish('sync:node:info:end', { data: data, id: os.hostname() + ':' + nconf.get('port') });
data.id = os.hostname() + ':' + nconf.get('port');
pubsub.publish('sync:node:info:end', { data: data, id: data.id });
});
});

View File

@@ -18,6 +18,7 @@ languagesController.get = function (req, res, next) {
res.render('admin/general/languages', {
languages: languages,
autoDetectLang: parseInt(meta.config.autoDetectLang, 10) === 1,
});
});
};

View File

@@ -5,6 +5,7 @@ var path = require('path');
var async = require('async');
var nconf = require('nconf');
var winston = require('winston');
var mime = require('mime');
var meta = require('../../meta');
var file = require('../../file');
@@ -102,6 +103,11 @@ uploadsController.uploadLogo = function (req, res, next) {
uploadsController.uploadSound = function (req, res, next) {
var uploadedFile = req.files.files[0];
var mimeType = mime.lookup(uploadedFile.name);
if (!/^audio\//.test(mimeType)) {
return next(Error('[[error:invalid-data]]'));
}
file.saveFileToLocal(uploadedFile.name, 'sounds', uploadedFile.path, function (err) {
if (err) {
return next(err);

View File

@@ -17,7 +17,7 @@ var translator = require('../translator');
var sockets = require('../socket.io');
var authenticationController = {};
var authenticationController = module.exports;
authenticationController.register = function (req, res) {
var registrationType = meta.config.registrationType || 'normal';
@@ -345,21 +345,21 @@ authenticationController.localLogin = function (req, username, password, next) {
var uid;
var userData = {};
if (!password || !utils.isPasswordValid(password)) {
return next(new Error('[[error:invalid-password]]'));
}
if (password.length > 4096) {
return next(new Error('[[error:password-too-long]]'));
}
async.waterfall([
function (next) {
user.isPasswordValid(password, next);
},
function (next) {
user.getUidByUserslug(userslug, next);
},
function (_uid, next) {
if (!_uid) {
return next(new Error('[[error:no-user]]'));
}
uid = _uid;
user.auth.logAttempt(uid, req.ip, next);
},
function (next) {
async.parallel({
userData: function (next) {
db.getObjectFields('user:' + uid, ['password', 'passwordExpiry'], next);
@@ -381,19 +381,18 @@ authenticationController.localLogin = function (req, username, password, next) {
return next(new Error('[[error:local-login-disabled]]'));
}
if (!userData || !userData.password) {
return next(new Error('[[error:invalid-user-data]]'));
}
if (result.banned) {
return banUser(uid, next);
}
user.auth.logAttempt(uid, req.ip, next);
},
function (next) {
Password.compare(password, userData.password, next);
},
function (passwordMatch, next) {
if (!passwordMatch) {
return next(new Error('[[error:invalid-password]]'));
return next(new Error('[[error:invalid-login-credentials]]'));
}
user.auth.clearLoginAttempts(uid);
next(null, userData, '[[success:authentication-successful]]');
@@ -447,5 +446,3 @@ function banUser(uid, next) {
}
});
}
module.exports = authenticationController;

View File

@@ -4,6 +4,7 @@ var async = require('async');
var plugins = require('../plugins');
var utils = require('../utils');
var db = require('./../database');
var batch = require('../batch');
module.exports = function (Groups) {
Groups.destroy = function (groupName, callback) {
@@ -29,14 +30,14 @@ module.exports = function (Groups) {
async.apply(db.delete, 'group:' + groupName + ':member:pids'),
async.apply(db.deleteObjectField, 'groupslug:groupname', utils.slugify(groupName)),
function (next) {
db.getSortedSetRange('groups:createtime', 0, -1, function (err, groups) {
if (err) {
return next(err);
}
async.each(groups, function (group, next) {
db.sortedSetRemove('group:' + group + ':members', groupName, next);
}, next);
});
batch.processSortedSet('groups:createtime', function (groupNames, next) {
var keys = groupNames.map(function (group) {
return 'group:' + group + ':members';
});
db.sortedSetsRemove(keys, groupName, next);
}, {
batch: 500,
}, next);
},
], function (err) {
if (err) {

View File

@@ -4,7 +4,7 @@ var fs = require('fs');
var path = require('path');
var async = require('async');
var Languages = {};
var Languages = module.exports;
var languagesPath = path.join(__dirname, '../build/public/language');
Languages.init = function (next) {
@@ -27,10 +27,13 @@ Languages.get = function (language, namespace, callback) {
});
};
Languages.list = function (callback) {
var languages = [];
var codeCache = null;
Languages.listCodes = function (callback) {
if (codeCache && codeCache.length) {
return callback(null, codeCache);
}
fs.readdir(languagesPath, function (err, files) {
fs.readFile(path.join(languagesPath, 'metadata.json'), function (err, buffer) {
if (err && err.code === 'ENOENT') {
return callback(null, []);
}
@@ -38,43 +41,59 @@ Languages.list = function (callback) {
return callback(err);
}
async.each(files, function (folder, next) {
fs.stat(path.join(languagesPath, folder), function (err, stat) {
if (err) {
return next(err);
}
var parsed;
try {
parsed = JSON.parse(buffer.toString());
} catch (e) {
return callback(e);
}
if (!stat.isDirectory()) {
return next();
}
var configPath = path.join(languagesPath, folder, 'language.json');
fs.readFile(configPath, function (err, buffer) {
if (err && err.code !== 'ENOENT') {
return next(err);
}
if (buffer) {
var lang = JSON.parse(buffer.toString());
if (lang.name && lang.code && lang.dir) {
languages.push(lang);
}
}
next();
});
});
}, function (err) {
if (err) {
return callback(err);
}
// Sort alphabetically
languages = languages.sort(function (a, b) {
return a.code > b.code ? 1 : -1;
});
callback(err, languages);
});
var langs = parsed.languages;
codeCache = langs;
callback(null, langs);
});
};
module.exports = Languages;
var listCache = null;
Languages.list = function (callback) {
if (listCache && listCache.length) {
return callback(null, listCache);
}
Languages.listCodes(function (err, codes) {
if (err) {
return callback(err);
}
async.map(codes, function (folder, next) {
var configPath = path.join(languagesPath, folder, 'language.json');
fs.readFile(configPath, function (err, buffer) {
if (err && err.code === 'ENOENT') {
return next();
}
if (err) {
return next(err);
}
try {
var lang = JSON.parse(buffer.toString());
next(null, lang);
} catch (e) {
next(e);
}
});
}, function (err, languages) {
if (err) {
return callback(err);
}
// filter out invalid ones
languages = languages.filter(function (lang) {
return lang && lang.code && lang.name && lang.dir;
});
listCache = languages;
callback(null, languages);
});
});
};

View File

@@ -78,6 +78,7 @@ module.exports = function (Meta) {
'public/src/modules/helpers.js',
'public/src/modules/string.js',
'public/src/modules/flags.js',
'public/src/modules/storage.js',
],
// modules listed below are built (/src/modules) so they can be defined anonymously

View File

@@ -100,6 +100,25 @@ function getTranslationTree(callback) {
});
},
// save a list of languages to `${buildLanguagesPath}/metadata.json`
// avoids readdirs later on
function (ref, next) {
async.waterfall([
function (next) {
mkdirp(buildLanguagesPath, next);
},
function (x, next) {
fs.writeFile(path.join(buildLanguagesPath, 'metadata.json'), JSON.stringify({
languages: ref.languages.sort(),
namespaces: ref.namespaces.sort(),
}), next);
},
function (next) {
next(null, ref);
},
], next);
},
// for each language and namespace combination,
// run through core and all plugins to generate
// a full translation hash

View File

@@ -1,13 +1,12 @@
'use strict';
var path = require('path');
var nconf = require('nconf');
var fs = require('fs');
var winston = require('winston');
module.exports = function (Meta) {
Meta.logs = {
path: path.join(nconf.get('base_dir'), 'logs', 'output.log'),
path: path.join(__dirname, '..', '..', 'logs', 'output.log'),
};
Meta.logs.get = function (callback) {

View File

@@ -41,6 +41,7 @@ module.exports = function (middleware) {
middleware.renderHeader = function (req, res, data, callback) {
var registrationType = meta.config.registrationType || 'normal';
res.locals.config = res.locals.config || {};
var templateValues = {
title: meta.config.title || '',
description: meta.config.description || '',
@@ -133,6 +134,7 @@ module.exports = function (middleware) {
templateValues.customJS = templateValues.useCustomJS ? meta.config.customJS : '';
templateValues.maintenanceHeader = parseInt(meta.config.maintenanceMode, 10) === 1 && !results.isAdmin;
templateValues.defaultLang = meta.config.defaultLang || 'en-GB';
templateValues.userLang = res.locals.config.userLang;
templateValues.privateUserInfo = parseInt(meta.config.privateUserInfo, 10) === 1;
templateValues.privateTagListing = parseInt(meta.config.privateTagListing, 10) === 1;

View File

@@ -9,6 +9,9 @@
};
module.compare = function (password, hash, callback) {
if (!hash || !password) {
return setImmediate(callback, null, false);
}
forkChild({ type: 'compare', password: password, hash: hash }, callback);
};

View File

@@ -138,39 +138,46 @@ module.exports = function (SocketPosts) {
return callback(new Error('[[error:invalid-data]]'));
}
var postData;
var topicData;
var isMainAndLast = false;
async.waterfall([
function (next) {
isMainAndLastPost(data.pid, next);
},
function (results, next) {
if (results.isMain && !results.isLast) {
return callback(new Error('[[error:cant-purge-main-post]]'));
return next(new Error('[[error:cant-purge-main-post]]'));
}
if (results.isMain && results.isLast) {
return deleteTopicOf(data.pid, socket, next);
}
setImmediate(next);
isMainAndLast = results.isMain && results.isLast;
posts.getPostFields(data.pid, ['toPid', 'tid'], next);
},
function (next) {
posts.getPostField(data.pid, 'toPid', next);
},
function (toPid, next) {
postData = { pid: data.pid, toPid: toPid };
function (_postData, next) {
postData = _postData;
postData.pid = data.pid;
posts.tools.purge(socket.uid, data.pid, next);
},
function (next) {
websockets.in('topic_' + data.tid).emit('event:post_purged', postData);
topics.getTopicField(data.tid, 'title', next);
topics.getTopicFields(data.tid, ['title', 'cid'], next);
},
function (title, next) {
function (_topicData, next) {
topicData = _topicData;
events.log({
type: 'post-purge',
uid: socket.uid,
pid: data.pid,
ip: socket.ip,
title: String(title),
title: String(topicData.title),
}, next);
},
function (next) {
if (isMainAndLast) {
socketTopics.doTopicAction('purge', 'event:topic_purged', socket, { tids: [postData.tid], cid: topicData.cid }, next);
} else {
setImmediate(next);
}
},
], callback);
};

View File

@@ -11,6 +11,9 @@ module.exports = function (User) {
User.auth = {};
User.auth.logAttempt = function (uid, ip, callback) {
if (!parseInt(uid, 10)) {
return setImmediate(callback);
}
async.waterfall([
function (next) {
db.exists('lockout:' + uid, next);

View File

@@ -5,6 +5,8 @@
</div>
<div class="panel-body">
<span>[[admin/development/info:nodes-responded, {nodeCount}, {timeOut}]]</span>
<table class="table table-striped">
<thead>
<tr>

View File

@@ -16,6 +16,17 @@
</select>
</div>
</form>
<form class="row">
<div class="form-group col-sm-6">
<div class="checkbox">
<label class="mdl-switch mdl-js-switch mdl-js-ripple-effect">
<input class="mdl-switch__input" type="checkbox" data-field="autoDetectLang" <!-- IF autoDetectLang -->checked<!-- ENDIF autoDetectLang -->/>
<span class="mdl-switch__label">[[admin/general/languages:auto-detect]]</span>
</label>
</div>
</div>
</form>
</div>
</div>
</div>

View File

@@ -55,13 +55,16 @@ module.exports.listen = function (callback) {
callback = callback || function () { };
emailer.registerApp(app);
setupExpressApp(app);
helpers.register();
logger.init(app);
async.waterfall([
function (next) {
setupExpressApp(app, next);
},
function (next) {
helpers.register();
logger.init(app);
next();
},
initializeNodeBB,
function (next) {
winston.info('NodeBB Ready');
@@ -110,7 +113,7 @@ function initializeNodeBB(callback) {
});
}
function setupExpressApp(app) {
function setupExpressApp(app, callback) {
var middleware = require('./middleware');
var relativePath = nconf.get('relative_path');
@@ -158,6 +161,8 @@ function setupExpressApp(app) {
var toobusy = require('toobusy-js');
toobusy.maxLag(parseInt(meta.config.eventLoopLagThreshold, 10) || 100);
toobusy.interval(parseInt(meta.config.eventLoopInterval, 10) || 500);
setupAutoLocale(app, callback);
}
function ping(req, res) {
@@ -195,6 +200,35 @@ function setupCookie() {
return cookie;
}
function setupAutoLocale(app, callback) {
languages.listCodes(function (err, codes) {
if (err) {
return callback(err);
}
var defaultLang = meta.config.defaultLang || 'en-GB';
var langs = [defaultLang].concat(codes).filter(function (el, i, arr) {
return arr.indexOf(el) === i;
});
app.use(function (req, res, next) {
if (parseInt(req.uid, 10) > 0 || parseInt(meta.config.autoDetectLang, 10) !== 1) {
return next();
}
var lang = req.acceptsLanguages(langs);
if (!lang) {
return next();
}
req.query.lang = lang;
next();
});
callback();
});
}
function listen(callback) {
callback = callback || function () { };
var port = parseInt(nconf.get('port'), 10);