diff --git a/core/server/api/authentication.js b/core/server/api/authentication.js index d46857510b..0e594fa427 100644 --- a/core/server/api/authentication.js +++ b/core/server/api/authentication.js @@ -309,7 +309,7 @@ authentication = { })); } - spamPrevention.userLogin.reset(opts.ip, tokenParts.email + 'login'); + spamPrevention.userLogin().reset(opts.ip, tokenParts.email + 'login'); return models.User.changePassword({ oldPassword: oldPassword, diff --git a/core/server/apps/amp/lib/helpers/amp_content.js b/core/server/apps/amp/lib/helpers/amp_content.js index 73acc6c95f..6a618bf819 100644 --- a/core/server/apps/amp/lib/helpers/amp_content.js +++ b/core/server/apps/amp/lib/helpers/amp_content.js @@ -8,19 +8,16 @@ // there if available. The cacheId is a combination of `updated_at` and the `slug`. var hbs = require('express-hbs'), Promise = require('bluebird'), - Amperize = require('amperize'), moment = require('moment'), - sanitizeHtml = require('sanitize-html'), logging = require('../../../../logging'), i18n = require('../../../../i18n'), errors = require('../../../../errors'), makeAbsoluteUrl = require('../../../../utils/make-absolute-urls'), utils = require('../../../../utils'), - cheerio = require('cheerio'), - amperize = new Amperize(), amperizeCache = {}, allowedAMPTags = [], allowedAMPAttributes = {}, + amperize, cleanHTML, ampHTML; @@ -120,6 +117,9 @@ function getAmperizeHTML(html, post) { return; } + var Amperize = require('amperize'); + amperize = amperize || new Amperize(); + // make relative URLs abolute html = makeAbsoluteUrl(html, utils.url.urlFor('home', true), post.url).html(); @@ -154,7 +154,9 @@ function getAmperizeHTML(html, post) { } function ampContent() { - var amperizeHTML = { + var sanitizeHtml = require('sanitize-html'), + cheerio = require('cheerio'), + amperizeHTML = { amperize: getAmperizeHTML(this.html, this) }; diff --git a/core/server/auth/oauth.js b/core/server/auth/oauth.js index df273abbd7..0380386854 100644 --- a/core/server/auth/oauth.js +++ b/core/server/auth/oauth.js @@ -17,7 +17,7 @@ function exchangeRefreshToken(client, refreshToken, scope, body, authInfo, done) var token = model.toJSON(); if (token.expires > Date.now()) { - spamPrevention.userLogin.reset(authInfo.ip, body.refresh_token + 'login'); + spamPrevention.userLogin().reset(authInfo.ip, body.refresh_token + 'login'); authUtils.createTokens({ clientId: token.client_id, @@ -54,7 +54,7 @@ function exchangePassword(client, username, password, scope, body, authInfo, don }); }) .then(function then(response) { - spamPrevention.userLogin.reset(authInfo.ip, username + 'login'); + spamPrevention.userLogin().reset(authInfo.ip, username + 'login'); return done(null, response.access_token, response.refresh_token, {expires_in: response.expires_in}); }); }) @@ -83,7 +83,7 @@ function exchangeAuthorizationCode(req, res, next) { })); } - spamPrevention.userLogin.reset(req.authInfo.ip, req.body.authorizationCode + 'login'); + spamPrevention.userLogin().reset(req.authInfo.ip, req.body.authorizationCode + 'login'); authUtils.createTokens({ clientId: req.client.id, diff --git a/core/server/mail/GhostMailer.js b/core/server/mail/GhostMailer.js index 9e0513ac94..cc1a3f4d9b 100644 --- a/core/server/mail/GhostMailer.js +++ b/core/server/mail/GhostMailer.js @@ -2,7 +2,6 @@ // Handles sending email for Ghost var _ = require('lodash'), Promise = require('bluebird'), - nodemailer = require('nodemailer'), validator = require('validator'), config = require('../config'), settingsCache = require('../settings/cache'), @@ -10,13 +9,12 @@ var _ = require('lodash'), utils = require('../utils'); function GhostMailer() { - var transport = config.get('mail') && config.get('mail').transport || 'direct', + var nodemailer = require('nodemailer'), + transport = config.get('mail') && config.get('mail').transport || 'direct', options = config.get('mail') && _.clone(config.get('mail').options) || {}; this.state = {}; - this.transport = nodemailer.createTransport(transport, options); - this.state.usingDirect = transport === 'direct'; } diff --git a/core/server/middleware/api/spam-prevention.js b/core/server/middleware/api/spam-prevention.js index d479f5999b..6b270eb1df 100644 --- a/core/server/middleware/api/spam-prevention.js +++ b/core/server/middleware/api/spam-prevention.js @@ -1,8 +1,4 @@ -var ExpressBrute = require('express-brute'), - BruteKnex = require('brute-knex'), - knexInstance = require('../../data/db/connection'), - store = new BruteKnex({tablename: 'brute', createTable:false, knex: knexInstance}), - moment = require('moment'), +var moment = require('moment'), errors = require('../../errors'), config = require('../../config'), spam = config.get('spam') || {}, @@ -14,9 +10,15 @@ var ExpressBrute = require('express-brute'), spamUserLogin = spam.user_login || {}, i18n = require('../../i18n'), + store, handleStoreError, globalBlock, globalReset, + privateBlogInstance, + globalResetInstance, + globalBlockInstance, + userLoginInstance, + userResetInstance, privateBlog, userLogin, userReset, @@ -46,93 +48,163 @@ handleStoreError = function handleStoreError(err) { // requests from a single IP // We allow for a generous number of requests here to prevent communites on the same IP bing barred on account of a single suer // Defaults to 50 attempts per hour and locks the endpoint for an hour -globalBlock = new ExpressBrute(store, - _.extend({ - attachResetToRequest: false, - failCallback: function (req, res, next, nextValidRequestDate) { - return next(new errors.TooManyRequestsError({ - message: 'Too many attempts try again in ' + moment(nextValidRequestDate).fromNow(true), - context: i18n.t('errors.middleware.spamprevention.forgottenPasswordIp.error', - {rfa: spamGlobalBlock.freeRetries + 1 || 5, rfp: spamGlobalBlock.lifetime || 60 * 60}), - help: i18n.t('errors.middleware.spamprevention.tooManyAttempts') - })); - }, - handleStoreError: handleStoreError - }, _.pick(spamGlobalBlock, spamConfigKeys)) -); +globalBlock = function globalBlock() { + var ExpressBrute = require('express-brute'), + BruteKnex = require('brute-knex'), + db = require('../../data/db'); -globalReset = new ExpressBrute(store, - _.extend({ - attachResetToRequest: false, - failCallback: function (req, res, next, nextValidRequestDate) { - // TODO use i18n again - return next(new errors.TooManyRequestsError({ - message: 'Too many attempts try again in ' + moment(nextValidRequestDate).fromNow(true), - context: i18n.t('errors.middleware.spamprevention.forgottenPasswordIp.error', - {rfa: spamGlobalReset.freeRetries + 1 || 5, rfp: spamGlobalReset.lifetime || 60 * 60}), - help: i18n.t('errors.middleware.spamprevention.forgottenPasswordIp.context') - })); - }, - handleStoreError: handleStoreError - }, _.pick(spamGlobalReset, spamConfigKeys)) -); + store = store || new BruteKnex({ + tablename: 'brute', + createTable: false, + knex: db.knex + }); + + globalBlockInstance = globalBlockInstance || new ExpressBrute(store, + _.extend({ + attachResetToRequest: false, + failCallback: function (req, res, next, nextValidRequestDate) { + return next(new errors.TooManyRequestsError({ + message: 'Too many attempts try again in ' + moment(nextValidRequestDate).fromNow(true), + context: i18n.t('errors.middleware.spamprevention.forgottenPasswordIp.error', + {rfa: spamGlobalBlock.freeRetries + 1 || 5, rfp: spamGlobalBlock.lifetime || 60 * 60}), + help: i18n.t('errors.middleware.spamprevention.tooManyAttempts') + })); + }, + handleStoreError: handleStoreError + }, _.pick(spamGlobalBlock, spamConfigKeys)) + ); + + return globalBlockInstance; +}; + +globalReset = function globalReset() { + var ExpressBrute = require('express-brute'), + BruteKnex = require('brute-knex'), + db = require('../../data/db'); + + store = store || new BruteKnex({ + tablename: 'brute', + createTable: false, + knex: db.knex + }); + + globalResetInstance = globalResetInstance || new ExpressBrute(store, + _.extend({ + attachResetToRequest: false, + failCallback: function (req, res, next, nextValidRequestDate) { + // TODO use i18n again + return next(new errors.TooManyRequestsError({ + message: 'Too many attempts try again in ' + moment(nextValidRequestDate).fromNow(true), + context: i18n.t('errors.middleware.spamprevention.forgottenPasswordIp.error', + {rfa: spamGlobalReset.freeRetries + 1 || 5, rfp: spamGlobalReset.lifetime || 60 * 60}), + help: i18n.t('errors.middleware.spamprevention.forgottenPasswordIp.context') + })); + }, + handleStoreError: handleStoreError + }, _.pick(spamGlobalReset, spamConfigKeys)) + ); + + return globalResetInstance; +}; // Stops login attempts for a user+IP pair with an increasing time period starting from 10 minutes // and rising to a week in a fibonnaci sequence // The user+IP count is reset when on successful login // Default value of 5 attempts per user+IP pair -userLogin = new ExpressBrute(store, - _.extend({ - attachResetToRequest: true, - failCallback: function (req, res, next, nextValidRequestDate) { - return next(new errors.TooManyRequestsError({ - message: 'Too many sign-in attempts try again in ' + moment(nextValidRequestDate).fromNow(true), - // TODO add more options to i18n - context: i18n.t('errors.middleware.spamprevention.tooManySigninAttempts.context'), - help: i18n.t('errors.middleware.spamprevention.tooManySigninAttempts.context') - })); - }, - handleStoreError: handleStoreError - }, _.pick(spamUserLogin, spamConfigKeys)) -); +userLogin = function userLogin() { + var ExpressBrute = require('express-brute'), + BruteKnex = require('brute-knex'), + db = require('../../data/db'); + + store = store || new BruteKnex({ + tablename: 'brute', + createTable: false, + knex: db.knex + }); + + userLoginInstance = userLoginInstance || new ExpressBrute(store, + _.extend({ + attachResetToRequest: true, + failCallback: function (req, res, next, nextValidRequestDate) { + return next(new errors.TooManyRequestsError({ + message: 'Too many sign-in attempts try again in ' + moment(nextValidRequestDate).fromNow(true), + // TODO add more options to i18n + context: i18n.t('errors.middleware.spamprevention.tooManySigninAttempts.context'), + help: i18n.t('errors.middleware.spamprevention.tooManySigninAttempts.context') + })); + }, + handleStoreError: handleStoreError + }, _.pick(spamUserLogin, spamConfigKeys)) + ); + + return userLoginInstance; +}; // Stop password reset requests when there are (freeRetries + 1) requests per lifetime per email // Defaults here are 5 attempts per hour for a user+IP pair // The endpoint is then locked for an hour -userReset = new ExpressBrute(store, - _.extend({ - attachResetToRequest: true, - failCallback: function (req, res, next, nextValidRequestDate) { - return next(new errors.TooManyRequestsError({ - message: 'Too many password reset attempts try again in ' + moment(nextValidRequestDate).fromNow(true), - context: i18n.t('errors.middleware.spamprevention.forgottenPasswordEmail.error', - {rfa: spamUserReset.freeRetries + 1 || 5, rfp: spamUserReset.lifetime || 60 * 60}), - help: i18n.t('errors.middleware.spamprevention.forgottenPasswordEmail.context') - })); - }, - handleStoreError: handleStoreError - }, _.pick(spamUserReset, spamConfigKeys)) -); +userReset = function userReset() { + var ExpressBrute = require('express-brute'), + BruteKnex = require('brute-knex'), + db = require('../../data/db'); + + store = store || new BruteKnex({ + tablename: 'brute', + createTable: false, + knex: db.knex + }); + + userResetInstance = userResetInstance || new ExpressBrute(store, + _.extend({ + attachResetToRequest: true, + failCallback: function (req, res, next, nextValidRequestDate) { + return next(new errors.TooManyRequestsError({ + message: 'Too many password reset attempts try again in ' + moment(nextValidRequestDate).fromNow(true), + context: i18n.t('errors.middleware.spamprevention.forgottenPasswordEmail.error', + {rfa: spamUserReset.freeRetries + 1 || 5, rfp: spamUserReset.lifetime || 60 * 60}), + help: i18n.t('errors.middleware.spamprevention.forgottenPasswordEmail.context') + })); + }, + handleStoreError: handleStoreError + }, _.pick(spamUserReset, spamConfigKeys)) + ); + + return userResetInstance; +}; // This protects a private blog from spam attacks. The defaults here allow 10 attempts per IP per hour // The endpoint is then locked for an hour -privateBlog = new ExpressBrute(store, - _.extend({ - attachResetToRequest: false, - failCallback: function (req, res, next, nextValidRequestDate) { - logging.error(new errors.GhostError({ - message: i18n.t('errors.middleware.spamprevention.tooManySigninAttempts.error', - {rfa: spamPrivateBlog.freeRetries + 1 || 5, rfp: spamPrivateBlog.lifetime || 60 * 60}), - context: i18n.t('errors.middleware.spamprevention.tooManySigninAttempts.context') - })); +privateBlog = function privateBlog() { + var ExpressBrute = require('express-brute'), + BruteKnex = require('brute-knex'), + db = require('../../data/db'); - return next(new errors.GhostError({ - message: 'Too many private sign-in attempts try again in ' + moment(nextValidRequestDate).fromNow(true) - })); - }, - handleStoreError: handleStoreError - }, _.pick(spamPrivateBlog, spamConfigKeys)) -); + store = store || new BruteKnex({ + tablename: 'brute', + createTable: false, + knex: db.knex + }); + + privateBlogInstance = privateBlogInstance || new ExpressBrute(store, + _.extend({ + attachResetToRequest: false, + failCallback: function (req, res, next, nextValidRequestDate) { + logging.error(new errors.GhostError({ + message: i18n.t('errors.middleware.spamprevention.tooManySigninAttempts.error', + {rfa: spamPrivateBlog.freeRetries + 1 || 5, rfp: spamPrivateBlog.lifetime || 60 * 60}), + context: i18n.t('errors.middleware.spamprevention.tooManySigninAttempts.context') + })); + + return next(new errors.GhostError({ + message: 'Too many private sign-in attempts try again in ' + moment(nextValidRequestDate).fromNow(true) + })); + }, + handleStoreError: handleStoreError + }, _.pick(spamPrivateBlog, spamConfigKeys)) + ); + + return privateBlogInstance; +}; module.exports = { globalBlock: globalBlock, diff --git a/core/server/middleware/brute.js b/core/server/middleware/brute.js index bc02380259..1ce27f731f 100644 --- a/core/server/middleware/brute.js +++ b/core/server/middleware/brute.js @@ -9,56 +9,69 @@ module.exports = { /** * block per route per ip */ - globalBlock: spamPrevention.globalBlock.getMiddleware({ - ignoreIP: false, - key: function (req, res, next) { - next(url.parse(req.url).pathname); - } - }), + globalBlock: function (req, res, next) { + return spamPrevention.globalBlock().getMiddleware({ + ignoreIP: false, + key: function (req, res, next) { + next(url.parse(req.url).pathname); + } + })(req, res, next); + }, /** * block per route per ip */ - globalReset: spamPrevention.globalReset.getMiddleware({ - ignoreIP: false, - key: function (req, res, next) { - next(url.parse(req.url).pathname); - } - }), + globalReset: function (req, res, next) { + return spamPrevention.globalReset().getMiddleware({ + ignoreIP: false, + key: function (req, res, next) { + next(url.parse(req.url).pathname); + } + })(req, res, next); + }, /** * block per user * username === email! */ - userLogin: spamPrevention.userLogin.getMiddleware({ - ignoreIP: false, - key: function (req, res, next) { - if (req.body.username) { - return next(req.body.username + 'login'); - } + userLogin: function (req, res, next) { + return spamPrevention.userLogin().getMiddleware({ + ignoreIP: false, + key: function (req, res, next) { + if (req.body.username) { + return next(req.body.username + 'login'); + } - if (req.body.authorizationCode) { - return next(req.body.authorizationCode + 'login'); - } + if (req.body.authorizationCode) { + return next(req.body.authorizationCode + 'login'); + } - if (req.body.refresh_token) { - return next(req.body.refresh_token + 'login'); - } + if (req.body.refresh_token) { + return next(req.body.refresh_token + 'login'); + } - return next(); - } - }), + return next(); + } + })(req, res, next); + }, /** * block per user */ - userReset: spamPrevention.userReset.getMiddleware({ - ignoreIP: false, - key: function (req, res, next) { - next(req.body.username + 'reset'); - } - }), - privateBlog: spamPrevention.privateBlog.getMiddleware({ - ignoreIP: false, - key: function (req, res, next) { - next('privateblog'); - } - }) + userReset: function (req, res, next) { + return spamPrevention.userReset().getMiddleware({ + ignoreIP: false, + key: function (req, res, next) { + next(req.body.username + 'reset'); + } + })(req, res, next); + }, + /** + * block per ip + */ + privateBlog: function (req, res, next) { + return spamPrevention.privateBlog().getMiddleware({ + ignoreIP: false, + key: function (req, res, next) { + next('privateblog'); + } + })(req, res, next); + } }; diff --git a/core/server/middleware/validation/blog-icon.js b/core/server/middleware/validation/blog-icon.js index 4e071f4b92..e1a2be58ea 100644 --- a/core/server/middleware/validation/blog-icon.js +++ b/core/server/middleware/validation/blog-icon.js @@ -1,6 +1,5 @@ var errors = require('../../errors'), config = require('../../config'), - ICO = require('icojs'), fs = require('fs'), Promise = require('bluebird'), sizeOf = require('image-size'), @@ -15,7 +14,8 @@ validIconSize = function validIconSize(size) { getIconDimensions = function getIconDimensions(icon) { return new Promise(function getImageSize(resolve, reject) { - var arrayBuffer; + var arrayBuffer, + ICO = require('icojs'); // image-size doesn't support .ico files if (icon.name.match(/.ico$/i)) { diff --git a/core/server/utils/zip-folder.js b/core/server/utils/zip-folder.js index 05422f43fa..f8af6ac912 100644 --- a/core/server/utils/zip-folder.js +++ b/core/server/utils/zip-folder.js @@ -1,8 +1,8 @@ -var archiver = require('archiver'), - fs = require('fs'); +var fs = require('fs'); module.exports = function zipFolder(folderToZip, destination, callback) { - var output = fs.createWriteStream(destination), + var archiver = require('archiver'), + output = fs.createWriteStream(destination), archive = archiver.create('zip', {}); output.on('close', function () { @@ -17,4 +17,3 @@ module.exports = function zipFolder(folderToZip, destination, callback) { archive.pipe(output); archive.finalize(); }; - diff --git a/core/test/unit/auth/oauth_spec.js b/core/test/unit/auth/oauth_spec.js index f438a52f5d..633f3c9f59 100644 --- a/core/test/unit/auth/oauth_spec.js +++ b/core/test/unit/auth/oauth_spec.js @@ -22,7 +22,7 @@ describe('OAuth', function () { res = {}; next = sandbox.spy(); - sandbox.stub(spamPrevention.userLogin, 'reset'); + sandbox.stub(spamPrevention.userLogin(), 'reset'); }); afterEach(function () { @@ -80,7 +80,7 @@ describe('OAuth', function () { json.should.have.property('expires_in'); json.should.have.property('token_type', 'Bearer'); next.called.should.eql(false); - spamPrevention.userLogin.reset.called.should.eql(true); + spamPrevention.userLogin().reset.called.should.eql(true); done(); } catch (err) { done(err);