diff --git a/Dockerfile b/Dockerfile index 89b051103d..94f93699a7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # The base image is the latest 8.x node (LTS) -FROM node:8.15.0@sha256:5aebe186c00da3308c8fde5b3a246d1927a56947a1b51f5c4308b7318adf74f4 +FROM node:8.15.0@sha256:cb66110c9c7d84bae9a6db8675f49d5c9e34d528023ef185b186e29ae5461051 RUN mkdir -p /usr/src/app WORKDIR /usr/src/app diff --git a/install/package.json b/install/package.json index 6f56bba6b0..73bbf4a763 100644 --- a/install/package.json +++ b/install/package.json @@ -32,7 +32,7 @@ "ace-builds": "^1.2.9", "archiver": "^3.0.0", "async": "2.6.1", - "autoprefixer": "^9.0.0", + "autoprefixer": "^9.4.6", "bcryptjs": "2.4.3", "benchpressjs": "^1.2.5", "body-parser": "^1.18.2", @@ -75,7 +75,7 @@ "material-design-lite": "^1.3.0", "mime": "^2.2.0", "mkdirp": "^0.5.1", - "mongodb": "3.1.12", + "mongodb": "3.1.13", "morgan": "^1.9.0", "mousetrap": "^1.6.1", "mubsub-nbb": "^1.5.0", @@ -90,16 +90,16 @@ "nodebb-plugin-spam-be-gone": "0.5.5", "nodebb-rewards-essentials": "0.0.13", "nodebb-theme-lavender": "5.0.8", - "nodebb-theme-persona": "9.1.10", + "nodebb-theme-persona": "9.1.13", "nodebb-theme-slick": "1.2.19", - "nodebb-theme-vanilla": "10.1.15", + "nodebb-theme-vanilla": "10.1.18", "nodebb-widget-essentials": "4.0.12", "nodemailer": "^5.0.0", "passport": "^0.4.0", "passport-local": "1.0.0", "pg": "^7.4.0", "pg-cursor": "^2.0.0", - "postcss": "7.0.12", + "postcss": "7.0.14", "postcss-clean": "1.1.0", "promise-polyfill": "^8.0.0", "prompt": "^1.0.0", @@ -110,7 +110,7 @@ "sanitize-html": "^1.16.3", "semver": "^5.4.1", "serve-favicon": "^2.4.5", - "sharp": "0.21.2", + "sharp": "0.21.3", "sitemap": "^2.0.0", "socket.io": "2.2.0", "socket.io-adapter-cluster": "^1.0.1", @@ -133,9 +133,9 @@ "@commitlint/cli": "7.3.2", "@commitlint/config-angular": "7.3.1", "coveralls": "3.0.2", - "eslint": "5.12.0", + "eslint": "5.12.1", "eslint-config-airbnb-base": "13.1.0", - "eslint-plugin-import": "2.14.0", + "eslint-plugin-import": "2.15.0", "grunt": "1.0.3", "grunt-contrib-watch": "1.1.0", "husky": "1.3.1", diff --git a/public/language/en-GB/flags.json b/public/language/en-GB/flags.json index 35fc87011a..9b8658dceb 100644 --- a/public/language/en-GB/flags.json +++ b/public/language/en-GB/flags.json @@ -9,6 +9,7 @@ "updated": "Updated", "target-purged": "The content this flag referred to has been purged and is no longer available.", + "graph-label": "Daily Flags", "quick-filters": "Quick Filters", "filter-active": "There are one or more filters active in this list of flags", "filter-reset": "Remove Filters", diff --git a/public/language/nl/admin/advanced/database.json b/public/language/nl/admin/advanced/database.json index a3187bb795..b31a089494 100644 --- a/public/language/nl/admin/advanced/database.json +++ b/public/language/nl/admin/advanced/database.json @@ -18,16 +18,16 @@ "mongo.resident-memory": "Resident geheugen", "mongo.virtual-memory": "Virtueel geheugen", "mongo.mapped-memory": "Mapped geheugen", - "mongo.bytes-in": "Bytes In", - "mongo.bytes-out": "Bytes Out", - "mongo.num-requests": "Number of Requests", + "mongo.bytes-in": "Bytes Inkomend", + "mongo.bytes-out": "Bytes Uitgaand", + "mongo.num-requests": "Aantal requests", "mongo.raw-info": "MongoDB Raw Info", "redis": "Redis", "redis.version": "Redis versie", - "redis.keys": "Keys", - "redis.expires": "Expires", - "redis.avg-ttl": "Average TTL", + "redis.keys": "Sleutels", + "redis.expires": "Verloopt", + "redis.avg-ttl": "Gemiddelde TTL", "redis.connected-clients": "Verbonden clients", "redis.connected-slaves": "Verbonden slaves", "redis.blocked-clients": "Geblokkeerde clients", diff --git a/public/language/nl/admin/general/dashboard.json b/public/language/nl/admin/general/dashboard.json index cfec2cd754..7e99e1f24b 100644 --- a/public/language/nl/admin/general/dashboard.json +++ b/public/language/nl/admin/general/dashboard.json @@ -49,7 +49,7 @@ "active-users.users": "Users", "active-users.guests": "Guests", "active-users.total": "Total", - "active-users.connections": "Connections", + "active-users.connections": "Connecties", "anonymous-registered-users": "Anonymous vs Registered Users", "anonymous": "Anonymous", @@ -71,6 +71,6 @@ "graphs.unique-visitors": "Unique Visitors", "graphs.registered-users": "Registered Users", "graphs.anonymous-users": "Anonymous Users", - "last-restarted-by": "Last restarted by", + "last-restarted-by": "Laatst herstart door", "no-users-browsing": "No users browsing" } diff --git a/public/language/nl/admin/manage/categories.json b/public/language/nl/admin/manage/categories.json index c14eadea0c..a48f5a9301 100644 --- a/public/language/nl/admin/manage/categories.json +++ b/public/language/nl/admin/manage/categories.json @@ -62,7 +62,7 @@ "alert.copy-success": "Settings Copied!", "alert.set-parent-category": "Set Parent Category", "alert.updated": "Updated Categories", - "alert.updated-success": "Category IDs %1 successfully updated.", + "alert.updated-success": "Category IDs %1 zijn succesvol geüpdatet.", "alert.upload-image": "Upload category image", "alert.find-user": "Find a User", "alert.user-search": "Search for a user here...", diff --git a/public/language/nl/admin/settings/general.json b/public/language/nl/admin/settings/general.json index c47481fe11..1977c9678a 100644 --- a/public/language/nl/admin/settings/general.json +++ b/public/language/nl/admin/settings/general.json @@ -6,7 +6,7 @@ "title.url-help": "Wanneer de titel word aangeklikt stuur gebruikers naar dit adress. Is het leeg gelaten dan worden gebruikers naar de forum hoofdpagina gestuurd.", "title.name": "Jouw Communiy Naam", "title.show-in-header": "Toon Site Titel in Header", - "browser-title": "Browser Title", + "browser-title": "Browser Titel", "browser-title-help": "Als geen browser titel is gespecificeerd dan word de site titel gebruikt", "title-layout": "Titel Lay-out", "title-layout-help": "Defineer hoe de browser titel gestructureerd word. bijv: {paginaTitel} | {browserTitel}", @@ -27,7 +27,7 @@ "favicon.upload": "Uploaden", "touch-icon": "Startscherm / Aanraakpictogram", "touch-icon.upload": "Uploaden", - "touch-icon.help": "Recommended size and format: 192x192, PNG format only. If no touch icon is specified, NodeBB will fall back to using the favicon.", + "touch-icon.help": "Aangeraden grootte en formaat: 192x192, alleen PNG formaat. Als er geen touch icoon is gespecificeerd zal NodeBB terugvallen op het favicon.", "outgoing-links": "Outgoing Links", "outgoing-links.warning-page": "Use Outgoing Links Warning Page", "search-default-sort-by": "Search default sort by", diff --git a/public/language/pt-PT/topic.json b/public/language/pt-PT/topic.json index c2dc7f548e..74945e942e 100644 --- a/public/language/pt-PT/topic.json +++ b/public/language/pt-PT/topic.json @@ -101,7 +101,7 @@ "composer.title_placeholder": "Insere aqui o título do tópico...", "composer.handle_placeholder": "Nome", "composer.discard": "Descartar", - "composer.submit": "Submeter", + "composer.submit": "Publicar", "composer.replying_to": "Respondendo a %1", "composer.new_topic": "Novo tópico", "composer.uploading": "carregando...", diff --git a/public/src/app.js b/public/src/app.js index bf68b520c8..c1a21be21e 100644 --- a/public/src/app.js +++ b/public/src/app.js @@ -149,7 +149,7 @@ app.cacheBuster = null; Unread.initUnreadTopics(); Notifications.prepareDOM(); Chat.prepareDOM(); - app.reskin(data.config.bootswatchSkin); + app.reskin(data.header.bootswatchSkin); translator.switchTimeagoLanguage(callback); bootbox.setLocale(config.userLang); @@ -194,6 +194,11 @@ app.cacheBuster = null; $(window).trigger('action:app.loggedOut', data); if (data.next) { + if (data.next.startsWith('http')) { + window.location.href = data.next; + return; + } + ajaxify.go(data.next); } else { ajaxify.refresh(); @@ -770,6 +775,17 @@ app.cacheBuster = null; return; } + var currentSkinClassName = $('body').attr('class').split(/\s+/).filter(function (className) { + return className.startsWith('skin-'); + }); + var currentSkin = currentSkinClassName[0].slice(5); + currentSkin = currentSkin !== 'noskin' ? currentSkin : ''; + + // Stop execution if skin didn't change + if (skinName === currentSkin) { + return; + } + var linkEl = document.createElement('link'); linkEl.rel = 'stylesheet'; linkEl.type = 'text/css'; @@ -778,13 +794,8 @@ app.cacheBuster = null; clientEl.parentNode.removeChild(clientEl); // Update body class with proper skin name - var currentSkinClassName = $('body').attr('class').split(/\s+/).filter(function (className) { - return className.startsWith('skin-'); - }); $('body').removeClass(currentSkinClassName.join(' ')); - if (skinName) { - $('body').addClass('skin-' + skinName); - } + $('body').addClass('skin-' + (skinName || 'noskin')); }; document.head.appendChild(linkEl); diff --git a/public/src/modules/translator.js b/public/src/modules/translator.js index 8bce483e44..0495b23306 100644 --- a/public/src/modules/translator.js +++ b/public/src/modules/translator.js @@ -586,6 +586,10 @@ if (!adaptor.timeagoShort) { var languageCode = utils.userLangToTimeagoCode(config.userLang); + if (!config.timeagoCodes.includes(languageCode + '-short')) { + languageCode = 'en'; + } + var originalSettings = assign({}, jQuery.timeago.settings.strings); jQuery.getScript(config.relative_path + '/assets/vendor/jquery/timeago/locales/jquery.timeago.' + languageCode + '-short.js').done(function () { adaptor.timeagoShort = assign({}, jQuery.timeago.settings.strings); @@ -602,6 +606,9 @@ delete adaptor.timeagoShort; var languageCode = utils.userLangToTimeagoCode(config.userLang); + if (!config.timeagoCodes.includes(languageCode + '-short')) { + languageCode = 'en'; + } jQuery.getScript(config.relative_path + '/assets/vendor/jquery/timeago/locales/jquery.timeago.' + languageCode + '.js').done(callback); }, diff --git a/src/controllers/404.js b/src/controllers/404.js index baa8d05650..b34926aa5e 100644 --- a/src/controllers/404.js +++ b/src/controllers/404.js @@ -9,7 +9,7 @@ var plugins = require('../plugins'); exports.handle404 = function handle404(req, res) { var relativePath = nconf.get('relative_path'); - var isClientScript = new RegExp('^' + relativePath + '\\/assets\\/src\\/.+\\.js$'); + var isClientScript = new RegExp('^' + relativePath + '\\/assets\\/src\\/.+\\.js(\\?v=\\w+)?$'); if (plugins.hasListeners('action:meta.override404')) { return plugins.fireHook('action:meta.override404', { diff --git a/src/controllers/api.js b/src/controllers/api.js index 5f37721935..746f67184f 100644 --- a/src/controllers/api.js +++ b/src/controllers/api.js @@ -12,6 +12,7 @@ var categories = require('../categories'); var privileges = require('../privileges'); var plugins = require('../plugins'); var translator = require('../translator'); +var languages = require('../languages'); var apiController = module.exports; @@ -62,6 +63,7 @@ apiController.loadConfig = function (req, callback) { config.bootswatchSkin = meta.config.bootswatchSkin || ''; config.enablePostHistory = (meta.config.enablePostHistory || 1) === 1; config.notificationAlertTimeout = meta.config.notificationAlertTimeout || 5000; + config.timeagoCodes = languages.timeagoCodes; if (config.useOutgoingLinksPage) { config.outgoingLinksWhitelist = meta.config['outgoingLinks:whitelist']; diff --git a/src/controllers/authentication.js b/src/controllers/authentication.js index 98b352050a..d4bf9620aa 100644 --- a/src/controllers/authentication.js +++ b/src/controllers/authentication.js @@ -498,10 +498,12 @@ authenticationController.logout = function (req, res, next) { return res.status(500); } - res.status(200).send({ + payload = { header: payload.header, config: res.locals.config, - }); + }; + plugins.fireHook('filter:user.logout', payload); + res.status(200).send(payload); }); } }, diff --git a/src/controllers/index.js b/src/controllers/index.js index ed94cd4713..494b0e9f02 100644 --- a/src/controllers/index.js +++ b/src/controllers/index.js @@ -92,7 +92,7 @@ Controllers.login = function (req, res, next) { var registrationType = meta.config.registrationType || 'normal'; var allowLoginWith = (meta.config.allowLoginWith || 'username-email'); - var returnTo = (req.headers['x-return-to'] || '').replace(nconf.get('base_url'), ''); + var returnTo = (req.headers['x-return-to'] || '').replace(nconf.get('base_url') + nconf.get('relative_path'), ''); var errorText; if (req.query.error === 'csrf-invalid') { @@ -214,7 +214,7 @@ Controllers.registerInterstitial = function (req, res, next) { // No interstitials, redirect to home const returnTo = req.session.returnTo || req.session.registration.returnTo; delete req.session.registration; - return helpers.redirect(res, returnTo || nconf.get('relative_path') + '/'); + return helpers.redirect(res, returnTo || '/'); } var renders = data.interstitials.map(function (interstitial) { return async.apply(req.app.render.bind(req.app), interstitial.template, interstitial.data || {}); @@ -252,6 +252,7 @@ Controllers.robots = function (req, res) { res.send('User-agent: *\n' + 'Disallow: ' + nconf.get('relative_path') + '/admin/\n' + 'Disallow: ' + nconf.get('relative_path') + '/reset/\n' + + 'Disallow: ' + nconf.get('relative_path') + '/compose\n' + 'Sitemap: ' + nconf.get('url') + '/sitemap.xml'); } }; diff --git a/src/middleware/header.js b/src/middleware/header.js index 38e5aa2bb3..65ec79d219 100644 --- a/src/middleware/header.js +++ b/src/middleware/header.js @@ -140,7 +140,9 @@ module.exports = function (middleware) { results.user['email:confirmed'] = results.user['email:confirmed'] === 1; results.user.isEmailConfirmSent = !!results.isEmailConfirmSent; - templateValues.bootswatchSkin = parseInt(meta.config.disableCustomUserSkins, 10) !== 1 ? res.locals.config.bootswatchSkin || '' : ''; + templateValues.bootswatchSkin = (parseInt(meta.config.disableCustomUserSkins, 10) !== 1 ? res.locals.config.bootswatchSkin : '') || meta.config.bootswatchSkin || ''; + templateValues.config.bootswatchSkin = templateValues.bootswatchSkin || 'noskin'; // TODO remove in v1.12.0+ + const unreadCounts = results.unreadData.counts; var unreadCount = { topic: unreadCounts[''] || 0, diff --git a/src/routes/authentication.js b/src/routes/authentication.js index 81af7fe550..a4341ddf36 100644 --- a/src/routes/authentication.js +++ b/src/routes/authentication.js @@ -3,10 +3,10 @@ var async = require('async'); var passport = require('passport'); var passportLocal = require('passport-local').Strategy; -var nconf = require('nconf'); var winston = require('winston'); var controllers = require('../controllers'); +var helpers = require('../controllers/helpers'); var plugins = require('../plugins'); var loginStrategies = []; @@ -88,10 +88,27 @@ Auth.reloadRoutes = function (router, callback) { // passport seems to remove `req.session.returnTo` after it redirects req.session.registration.returnTo = req.session.returnTo; next(); - }, passport.authenticate(strategy.name, { - successReturnToOrRedirect: nconf.get('relative_path') + (strategy.successUrl !== undefined ? strategy.successUrl : '/'), - failureRedirect: nconf.get('relative_path') + (strategy.failureUrl !== undefined ? strategy.failureUrl : '/login'), - })); + }, function (req, res, next) { + passport.authenticate(strategy.name, function (err, user) { + if (err) { + delete req.session.registration; + return next(err); + } + + if (!user) { + delete req.session.registration; + return helpers.redirect(res, strategy.failureUrl !== undefined ? strategy.failureUrl : '/login'); + } + + req.login(user, function (err) { + if (err) { + return next(err); + } + + helpers.redirect(res, strategy.successUrl !== undefined ? strategy.successUrl : '/'); + }); + })(req, res, next); + }); }); router.post('/register', Auth.middleware.applyCSRF, Auth.middleware.applyBlacklist, controllers.authentication.register); diff --git a/test/uploads.js b/test/uploads.js index 077ce55704..578b99515d 100644 --- a/test/uploads.js +++ b/test/uploads.js @@ -158,14 +158,21 @@ describe('Upload Controllers', function () { it('should fail if file is not an image', function (done) { file.isFileTypeAllowed(path.join(__dirname, '../test/files/notanimage.png'), function (err) { - assert.equal(err.message, 'Input file is missing or of an unsupported image format'); + assert.equal(err.message, 'Input file contains unsupported image format'); done(); }); }); it('should fail if file is not an image', function (done) { image.size(path.join(__dirname, '../test/files/notanimage.png'), function (err) { - assert.equal(err.message, 'Input file is missing or of an unsupported image format'); + assert.equal(err.message, 'Input file contains unsupported image format'); + done(); + }); + }); + + it('should fail if file is missing', function (done) { + image.size(path.join(__dirname, '../test/files/doesnotexist.png'), function (err) { + assert.equal(err.message, 'Input file is missing'); done(); }); });