diff --git a/src/controllers/404.js b/src/controllers/404.js index 3843d6bace..fa64ab9f87 100644 --- a/src/controllers/404.js +++ b/src/controllers/404.js @@ -55,6 +55,7 @@ exports.send404 = async function (req, res) { }); } + await middleware.inhibitCacheAsync(req, res); await middleware.buildHeaderAsync(req, res); await res.render('404', { path: validator.escape(path), diff --git a/src/middleware/headers.js b/src/middleware/headers.js index dacfb62dec..99d817eaa5 100644 --- a/src/middleware/headers.js +++ b/src/middleware/headers.js @@ -3,6 +3,7 @@ const os = require('os'); const winston = require('winston'); const _ = require('lodash'); +const util = require('util'); const meta = require('../meta'); const languages = require('../languages'); @@ -108,4 +109,13 @@ module.exports = function (middleware) { return [defaultLang]; } } + + middleware.inhibitCache = (req, res, next) => { + if (req.loggedIn) { + res.set('cache-control', 'private'); + } + + next(); + }; + middleware.inhibitCacheAsync = util.promisify(middleware.inhibitCache); }; diff --git a/src/routes/helpers.js b/src/routes/helpers.js index 8bcad07b22..5b680e13f2 100644 --- a/src/routes/helpers.js +++ b/src/routes/helpers.js @@ -18,6 +18,7 @@ function _handleArgs(middleware, middlewares, controller) { middleware.authenticateRequest, middleware.maintenanceMode, middleware.registrationComplete, + middleware.inhibitCache, middleware.pluginHooks, ...middlewares, ]; diff --git a/test/middleware.js b/test/middleware.js index c197b0757a..9c5cb71ca5 100644 --- a/test/middleware.js +++ b/test/middleware.js @@ -1,20 +1,25 @@ 'use strict'; const assert = require('assert'); +const nconf = require('nconf'); +const request = require('request-promise-native'); const db = require('./mocks/databasemock'); const middleware = require('../src/middleware'); const user = require('../src/user'); const groups = require('../src/groups'); +const utils = require('../src/utils'); + +const helpers = require('./helpers'); describe('Middlewares', () => { - let adminUid; + describe('expose', () => { + let adminUid; - before(async () => { - adminUid = await user.create({ username: 'admin', password: '123456' }); - await groups.join('administrators', adminUid); - }); + before(async () => { + adminUid = await user.create({ username: 'admin', password: '123456' }); + await groups.join('administrators', adminUid); + }); - describe('expose', () => { it('should expose res.locals.isAdmin = false', (done) => { const resMock = { locals: {} }; middleware.exposeAdmin({}, resMock, () => { @@ -94,5 +99,93 @@ describe('Middlewares', () => { }); }); }); + + describe('.inhibitCache (cache-control header)', () => { + let uid; + let jar; + + before(async () => { + uid = await user.create({ username: 'testuser', password: '123456' }); + ({ jar } = await helpers.loginUser('testuser', '123456')); + }); + + it('should be absent on non-existent routes, for guests', async () => { + const res = await request(`${nconf.get('url')}/${utils.generateUUID()}`, { + simple: false, + resolveWithFullResponse: true, + }); + + assert.strictEqual(res.statusCode, 404); + assert(!Object.keys(res.headers).includes('cache-control')); + }); + + it('should be set to "private" on non-existent routes, for logged in users', async () => { + const res = await request(`${nconf.get('url')}/${utils.generateUUID()}`, { + simple: false, + resolveWithFullResponse: true, + jar, + }); + + assert.strictEqual(res.statusCode, 404); + assert(Object.keys(res.headers).includes('cache-control')); + assert.strictEqual(res.headers['cache-control'], 'private'); + }); + + it('should be absent on regular routes, for guests', async () => { + const res = await request(nconf.get('url'), { + simple: false, + resolveWithFullResponse: true, + }); + + assert.strictEqual(res.statusCode, 200); + assert(!Object.keys(res.headers).includes('cache-control')); + }); + + it('should be absent on api routes, for guests', async () => { + const res = await request(`${nconf.get('url')}/api`, { + simple: false, + resolveWithFullResponse: true, + }); + + assert.strictEqual(res.statusCode, 200); + assert(!Object.keys(res.headers).includes('cache-control')); + }); + + it('should be set to "private" on regular routes, for logged-in users', async () => { + const res = await request(nconf.get('url'), { + simple: false, + resolveWithFullResponse: true, + jar, + }); + + assert.strictEqual(res.statusCode, 200); + assert(Object.keys(res.headers).includes('cache-control')); + assert.strictEqual(res.headers['cache-control'], 'private'); + }); + + it('should be set to "private" on api routes, for logged-in users', async () => { + const res = await request(`${nconf.get('url')}/api`, { + simple: false, + resolveWithFullResponse: true, + jar, + }); + + assert.strictEqual(res.statusCode, 200); + assert(Object.keys(res.headers).includes('cache-control')); + assert.strictEqual(res.headers['cache-control'], 'private'); + }); + + it('should be set to "private" on apiv3 routes, for logged-in users', async () => { + const res = await request(`${nconf.get('url')}/api/v3/users/${uid}`, { + simple: false, + resolveWithFullResponse: true, + jar, + }); + + assert.strictEqual(res.statusCode, 200); + assert(Object.keys(res.headers).includes('cache-control')); + assert.strictEqual(res.headers['cache-control'], 'private'); + }); + }); });