diff --git a/test/api/v3/unit/middlewares/redirects.js b/test/api/v3/unit/middlewares/redirects.js new file mode 100644 index 0000000000..ed210c169a --- /dev/null +++ b/test/api/v3/unit/middlewares/redirects.js @@ -0,0 +1,182 @@ +import { + generateRes, + generateReq, + generateNext, +} from '../../../../helpers/api-unit.helper'; +import nconf from 'nconf'; +import requireAgain from 'require-again'; + +describe('redirects middleware', () => { + let res, req, next; + let pathToRedirectsMiddleware = '../../../../../website/server/middlewares/redirects'; + + beforeEach(() => { + res = generateRes(); + req = generateReq(); + next = generateNext(); + }); + + context('forceSSL', () => { + it('sends http requests to https', () => { + let nconfStub = sandbox.stub(nconf, 'get'); + nconfStub.withArgs('BASE_URL').returns('https://habitica.com'); + nconfStub.withArgs('IS_PROD').returns(true); + req.header = sandbox.stub().withArgs('x-forwarded-proto').returns('http'); + req.originalUrl = '/static/front'; + + let attachRedirects = requireAgain(pathToRedirectsMiddleware); + + attachRedirects.forceSSL(req, res, next); + + expect(res.redirect).to.be.calledOnce; + expect(res.redirect).to.be.calledWith('https://habitica.com/static/front'); + }); + + it('does not redirect https forwarded requests', () => { + let nconfStub = sandbox.stub(nconf, 'get'); + nconfStub.withArgs('BASE_URL').returns('https://habitica.com'); + nconfStub.withArgs('IS_PROD').returns(true); + req.header = sandbox.stub().withArgs('x-forwarded-proto').returns('https'); + req.originalUrl = '/static/front'; + + let attachRedirects = requireAgain(pathToRedirectsMiddleware); + + attachRedirects.forceSSL(req, res, next); + + expect(res.redirect).to.be.notCalled; + }); + + it('does not redirect outside of production environments', () => { + let nconfStub = sandbox.stub(nconf, 'get'); + nconfStub.withArgs('BASE_URL').returns('https://habitica.com'); + nconfStub.withArgs('IS_PROD').returns(false); + req.header = sandbox.stub().withArgs('x-forwarded-proto').returns('http'); + req.originalUrl = '/static/front'; + + let attachRedirects = requireAgain(pathToRedirectsMiddleware); + + attachRedirects.forceSSL(req, res, next); + + expect(res.redirect).to.be.notCalled; + }); + + it('does not redirect if base URL is not https', () => { + let nconfStub = sandbox.stub(nconf, 'get'); + nconfStub.withArgs('BASE_URL').returns('http://habitica.com'); + nconfStub.withArgs('IS_PROD').returns(true); + req.header = sandbox.stub().withArgs('x-forwarded-proto').returns('http'); + req.originalUrl = '/static/front'; + + let attachRedirects = requireAgain(pathToRedirectsMiddleware); + + attachRedirects.forceSSL(req, res, next); + + expect(res.redirect).to.be.notCalled; + }); + }); + + context('forceHabitica', () => { + it('sends requests with differing hostname to base URL host', () => { + let nconfStub = sandbox.stub(nconf, 'get'); + nconfStub.withArgs('BASE_URL').returns('https://habitica.com'); + nconfStub.withArgs('IGNORE_REDIRECT').returns('false'); + nconfStub.withArgs('IS_PROD').returns(true); + req.hostname = 'www.habitica.com'; + req.method = 'GET'; + req.originalUrl = '/static/front'; + req.url = '/static/front'; + + let attachRedirects = requireAgain(pathToRedirectsMiddleware); + + attachRedirects.forceHabitica(req, res, next); + + expect(res.redirect).to.be.calledOnce; + expect(res.redirect).to.be.calledWith(301, 'https://habitica.com/static/front'); + }); + + it('does not redirect outside of production environments', () => { + let nconfStub = sandbox.stub(nconf, 'get'); + nconfStub.withArgs('BASE_URL').returns('https://habitica.com'); + nconfStub.withArgs('IGNORE_REDIRECT').returns('false'); + nconfStub.withArgs('IS_PROD').returns(false); + req.hostname = 'www.habitica.com'; + req.method = 'GET'; + req.originalUrl = '/static/front'; + req.url = '/static/front'; + + let attachRedirects = requireAgain(pathToRedirectsMiddleware); + + attachRedirects.forceHabitica(req, res, next); + + expect(res.redirect).to.be.notCalled; + }); + + it('does not redirect if env is set to ignore redirection', () => { + let nconfStub = sandbox.stub(nconf, 'get'); + nconfStub.withArgs('BASE_URL').returns('https://habitica.com'); + nconfStub.withArgs('IGNORE_REDIRECT').returns('true'); + nconfStub.withArgs('IS_PROD').returns(true); + req.hostname = 'www.habitica.com'; + req.method = 'GET'; + req.originalUrl = '/static/front'; + req.url = '/static/front'; + + let attachRedirects = requireAgain(pathToRedirectsMiddleware); + + attachRedirects.forceHabitica(req, res, next); + + expect(res.redirect).to.be.notCalled; + }); + + it('does not redirect if request hostname matches base URL host', () => { + let nconfStub = sandbox.stub(nconf, 'get'); + nconfStub.withArgs('BASE_URL').returns('https://habitica.com'); + nconfStub.withArgs('IGNORE_REDIRECT').returns('false'); + nconfStub.withArgs('IS_PROD').returns(true); + req.hostname = 'habitica.com'; + req.method = 'GET'; + req.originalUrl = '/static/front'; + req.url = '/static/front'; + + let attachRedirects = requireAgain(pathToRedirectsMiddleware); + + attachRedirects.forceHabitica(req, res, next); + + expect(res.redirect).to.be.notCalled; + }); + + it('does not redirect if request is an API URL', () => { + let nconfStub = sandbox.stub(nconf, 'get'); + nconfStub.withArgs('BASE_URL').returns('https://habitica.com'); + nconfStub.withArgs('IGNORE_REDIRECT').returns('false'); + nconfStub.withArgs('IS_PROD').returns(true); + req.hostname = 'www.habitica.com'; + req.method = 'GET'; + req.originalUrl = '/api/v3/challenges'; + req.url = '/api/v3/challenges'; + + let attachRedirects = requireAgain(pathToRedirectsMiddleware); + + attachRedirects.forceHabitica(req, res, next); + + expect(res.redirect).to.be.notCalled; + }); + + it('does not redirect if request method is not GET', () => { + let nconfStub = sandbox.stub(nconf, 'get'); + nconfStub.withArgs('BASE_URL').returns('https://habitica.com'); + nconfStub.withArgs('IGNORE_REDIRECT').returns('false'); + nconfStub.withArgs('IS_PROD').returns(true); + req.hostname = 'www.habitica.com'; + req.method = 'POST'; + req.originalUrl = '/static/front'; + req.url = '/static/front'; + + let attachRedirects = requireAgain(pathToRedirectsMiddleware); + + attachRedirects.forceHabitica(req, res, next); + + expect(res.redirect).to.be.notCalled; + }); + }); +}); diff --git a/test/helpers/api-unit.helper.js b/test/helpers/api-unit.helper.js index 09fdfcba60..9738c45b41 100644 --- a/test/helpers/api-unit.helper.js +++ b/test/helpers/api-unit.helper.js @@ -30,16 +30,17 @@ export function generateChallenge (options = {}) { export function generateRes (options = {}) { let defaultRes = { - render: sandbox.stub(), - send: sandbox.stub(), - status: sandbox.stub().returnsThis(), - sendStatus: sandbox.stub().returnsThis(), json: sandbox.stub(), locals: { user: generateUser(options.localsUser), group: generateGroup(options.localsGroup), }, + redirect: sandbox.stub(), + render: sandbox.stub(), + send: sandbox.stub(), + sendStatus: sandbox.stub().returnsThis(), set: sandbox.stub(), + status: sandbox.stub().returnsThis(), t (string) { return i18n.t(string); }, diff --git a/website/server/middlewares/redirects.js b/website/server/middlewares/redirects.js index a907a89b14..9bc40cb439 100644 --- a/website/server/middlewares/redirects.js +++ b/website/server/middlewares/redirects.js @@ -1,9 +1,12 @@ import nconf from 'nconf'; const IS_PROD = nconf.get('IS_PROD'); -const IGNORE_REDIRECT = nconf.get('IGNORE_REDIRECT'); +const IGNORE_REDIRECT = nconf.get('IGNORE_REDIRECT') === 'true'; const BASE_URL = nconf.get('BASE_URL'); +let baseUrlSplit = BASE_URL.split('//'); +const BASE_URL_HOST = baseUrlSplit[1]; + function isHTTP (req) { return ( // eslint-disable-line no-extra-parens req.header('x-forwarded-proto') && @@ -13,15 +16,8 @@ function isHTTP (req) { ); } -function isProxied (req) { - return ( // eslint-disable-line no-extra-parens - req.header('x-habitica-lb') && - req.header('x-habitica-lb') === 'Yes' - ); -} - export function forceSSL (req, res, next) { - if (isHTTP(req) && !isProxied(req)) { + if (isHTTP(req)) { return res.redirect(BASE_URL + req.originalUrl); } @@ -35,7 +31,7 @@ function nonApiUrl (req) { } export function forceHabitica (req, res, next) { - if (IS_PROD && !IGNORE_REDIRECT && !isProxied(req) && nonApiUrl(req)) { + if (IS_PROD && !IGNORE_REDIRECT && req.hostname !== BASE_URL_HOST && nonApiUrl(req) && req.method === 'GET') { return res.redirect(301, BASE_URL + req.url); }