diff --git a/core/frontend/services/routing/TaxonomyRouter.js b/core/frontend/services/routing/TaxonomyRouter.js index 387aa73252..031f142c9b 100644 --- a/core/frontend/services/routing/TaxonomyRouter.js +++ b/core/frontend/services/routing/TaxonomyRouter.js @@ -1,4 +1,5 @@ const debug = require('ghost-ignition').debug('services:routing:taxonomy-router'); +const config = require('../../../server/config'); const common = require('../../../server/lib/common'); const ParentRouter = require('./ParentRouter'); const RSSRouter = require('./RSSRouter'); @@ -53,7 +54,9 @@ class TaxonomyRouter extends ParentRouter { this.mountRoute(urlUtils.urlJoin(this.permalinks.value, 'page', ':page(\\d+)'), controllers.channel); // REGISTER: edit redirect to admin client e.g. /tag/:slug/edit - this.mountRoute(urlUtils.urlJoin(this.permalinks.value, 'edit'), this._redirectEditOption.bind(this)); + if (config.get('admin:redirects')) { + this.mountRoute(urlUtils.urlJoin(this.permalinks.value, 'edit'), this._redirectEditOption.bind(this)); + } common.events.emit('router.created', this); } diff --git a/core/frontend/services/routing/controllers/entry.js b/core/frontend/services/routing/controllers/entry.js index 571fbcdeee..53d906adf0 100644 --- a/core/frontend/services/routing/controllers/entry.js +++ b/core/frontend/services/routing/controllers/entry.js @@ -1,8 +1,9 @@ -const debug = require('ghost-ignition').debug('services:routing:controllers:entry'), - url = require('url'), - urlService = require('../../../services/url'), - urlUtils = require('../../../../server/lib/url-utils'), - helpers = require('../helpers'); +const debug = require('ghost-ignition').debug('services:routing:controllers:entry'); +const url = require('url'); +const config = require('../../../../server/config'); +const urlService = require('../../../services/url'); +const urlUtils = require('../../../../server/lib/url-utils'); +const helpers = require('../helpers'); /** * @description Entry controller. @@ -32,6 +33,11 @@ module.exports = function entryController(req, res, next) { // CASE: last param is of url is /edit, redirect to admin if (lookup.isEditURL) { + if (!config.get('admin:redirects')) { + debug('is edit url but admin redirects are disabled'); + return next(); + } + debug('redirect. is edit url'); const resourceType = entry.page ? 'page' : 'post'; diff --git a/core/frontend/services/routing/controllers/preview.js b/core/frontend/services/routing/controllers/preview.js index 4f8632dc97..ca4624ce4b 100644 --- a/core/frontend/services/routing/controllers/preview.js +++ b/core/frontend/services/routing/controllers/preview.js @@ -1,7 +1,8 @@ -const debug = require('ghost-ignition').debug('services:routing:controllers:preview'), - urlService = require('../../url'), - urlUtils = require('../../../../server/lib/url-utils'), - helpers = require('../helpers'); +const debug = require('ghost-ignition').debug('services:routing:controllers:preview'); +const config = require('../../../../server/config'); +const urlService = require('../../url'); +const urlUtils = require('../../../../server/lib/url-utils'); +const helpers = require('../helpers'); /** * @description Preview Controller. @@ -31,6 +32,11 @@ module.exports = function previewController(req, res, next) { } if (req.params.options && req.params.options.toLowerCase() === 'edit') { + // CASE: last param of the url is /edit but admin redirects are disabled + if (!config.get('admin:redirects')) { + return next(); + } + // @TODO: we don't know which resource type it is, because it's a generic preview handler and the // preview API returns {previews: []} // @TODO: figure out how to solve better diff --git a/core/server/config/defaults.json b/core/server/config/defaults.json index a253eec8df..1382f66492 100644 --- a/core/server/config/defaults.json +++ b/core/server/config/defaults.json @@ -4,6 +4,9 @@ "host": "127.0.0.1", "port": 2368 }, + "admin": { + "redirects": true + }, "updateCheck": { "url": "https://updates.ghost.org", "forceUpdate": false diff --git a/core/server/web/shared/middlewares/admin-redirects.js b/core/server/web/shared/middlewares/admin-redirects.js index 9a0fba0863..7670714a3a 100644 --- a/core/server/web/shared/middlewares/admin-redirects.js +++ b/core/server/web/shared/middlewares/admin-redirects.js @@ -1,4 +1,5 @@ const express = require('express'); +const config = require('../../../config'); const urlUtils = require('../../../lib/url-utils'); const adminRedirect = (path) => { @@ -10,6 +11,10 @@ const adminRedirect = (path) => { // redirect to /ghost to the admin module.exports = function adminRedirects() { const router = express.Router(); - router.get(/^\/ghost\/?$/, adminRedirect('/')); + + if (config.get('admin:redirects')) { + router.get(/^\/ghost\/?$/, adminRedirect('/')); + } + return router; }; diff --git a/core/test/regression/site/dynamic_routing_spec.js b/core/test/regression/site/dynamic_routing_spec.js index ff2067c11c..a7e26562a7 100644 --- a/core/test/regression/site/dynamic_routing_spec.js +++ b/core/test/regression/site/dynamic_routing_spec.js @@ -2,17 +2,18 @@ // As it stands, these tests depend on the database, and as such are integration tests. // These tests are here to cover the headers sent with requests and high-level redirects that can't be // tested with the unit tests -const should = require('should'), - supertest = require('supertest'), - sinon = require('sinon'), - moment = require('moment'), - path = require('path'), - testUtils = require('../../utils'), - cheerio = require('cheerio'), - config = require('../../../server/config'), - api = require('../../../server/api'), - settingsCache = require('../../../server/services/settings/cache'), - ghost = testUtils.startGhost; +const should = require('should'); +const supertest = require('supertest'); +const sinon = require('sinon'); +const moment = require('moment'); +const path = require('path'); +const testUtils = require('../../utils'); +const configUtils = require('../../utils/configUtils'); +const cheerio = require('cheerio'); +const config = require('../../../server/config'); +const api = require('../../../server/api'); +const settingsCache = require('../../../server/services/settings/cache'); +const ghost = testUtils.startGhost; let request; @@ -284,6 +285,43 @@ describe('Dynamic Routing', function () { }); }); + describe('Edit with admin redirects disabled', function () { + before(function () { + configUtils.set('admin:redirects', false); + + return ghost({forceStart: true}) + .then(function (_ghostServer) { + ghostServer = _ghostServer; + request = supertest.agent(config.get('url')); + }); + }); + + after(function () { + configUtils.restore(); + + return ghost({forceStart: true}) + .then(function (_ghostServer) { + ghostServer = _ghostServer; + request = supertest.agent(config.get('url')); + }); + }); + + it('should redirect without slash', function (done) { + request.get('/tag/getting-started/edit') + .expect('Location', '/tag/getting-started/edit/') + .expect('Cache-Control', testUtils.cacheRules.year) + .expect(301) + .end(doEnd(done)); + }); + + it('should not redirect to admin', function (done) { + request.get('/tag/getting-started/edit/') + .expect(404) + .expect('Cache-Control', testUtils.cacheRules.private) + .end(doEnd(done)); + }); + }); + describe.skip('Paged', function () { // Inserting more posts takes a bit longer this.timeout(20000); @@ -529,6 +567,43 @@ describe('Dynamic Routing', function () { }); }); + describe('Edit with admin redirects disabled', function () { + before(function () { + configUtils.set('admin:redirects', false); + + return ghost({forceStart: true}) + .then(function (_ghostServer) { + ghostServer = _ghostServer; + request = supertest.agent(config.get('url')); + }); + }); + + after(function () { + configUtils.restore(); + + return ghost({forceStart: true}) + .then(function (_ghostServer) { + ghostServer = _ghostServer; + request = supertest.agent(config.get('url')); + }); + }); + + it('should redirect without slash', function (done) { + request.get('/author/ghost-owner/edit') + .expect('Location', '/author/ghost-owner/edit/') + .expect('Cache-Control', testUtils.cacheRules.year) + .expect(301) + .end(doEnd(done)); + }); + + it('should not redirect to admin', function (done) { + request.get('/author/ghost-owner/edit/') + .expect('Cache-Control', testUtils.cacheRules.private) + .expect(404) + .end(doEnd(done)); + }); + }); + describe('Paged', function () { // Add enough posts to trigger pages before(function (done) { diff --git a/core/test/regression/site/frontend_spec.js b/core/test/regression/site/frontend_spec.js index 8d0135324e..d4fb3d5339 100644 --- a/core/test/regression/site/frontend_spec.js +++ b/core/test/regression/site/frontend_spec.js @@ -193,6 +193,41 @@ describe('Frontend Routing', function () { }); }); + describe('Post edit with admin redirects disabled', function () { + before(function () { + configUtils.set('admin:redirects', false); + + return ghost({forceStart: true}) + .then(function () { + request = supertest.agent(config.get('url')); + }); + }); + + after(function () { + configUtils.restore(); + + return ghost({forceStart: true}) + .then(function () { + request = supertest.agent(config.get('url')); + }); + }); + + it('should redirect without slash', function (done) { + request.get('/welcome/edit') + .expect('Location', '/welcome/edit/') + .expect('Cache-Control', testUtils.cacheRules.year) + .expect(301) + .end(doEnd(done)); + }); + + it('should not redirect to editor', function (done) { + request.get('/welcome/edit/') + .expect('Cache-Control', testUtils.cacheRules.private) + .expect(404) + .end(doEnd(done)); + }); + }); + describe('AMP post', function () { it('should redirect without slash', function (done) { request.get('/welcome/amp') @@ -357,6 +392,43 @@ describe('Frontend Routing', function () { }); }); + describe('edit with admin redirects disabled', function () { + before(function (done) { + configUtils.set('admin:redirects', false); + + ghost({forceStart: true}) + .then(function () { + request = supertest.agent(config.get('url')); + addPosts(done); + }); + }); + + after(function (done) { + configUtils.restore(); + + ghost({forceStart: true}) + .then(function () { + request = supertest.agent(config.get('url')); + addPosts(done); + }); + }); + + it('should redirect without slash', function (done) { + request.get('/static-page-test/edit') + .expect('Location', '/static-page-test/edit/') + .expect('Cache-Control', testUtils.cacheRules.year) + .expect(301) + .end(doEnd(done)); + }); + + it('should not redirect to editor', function (done) { + request.get('/static-page-test/edit/') + .expect(404) + .expect('Cache-Control', testUtils.cacheRules.private) + .end(doEnd(done)); + }); + }); + describe('amp', function () { it('should 404 for amp parameter', function (done) { // NOTE: only post pages are supported so the router doesn't have a way to distinguish if diff --git a/core/test/regression/site/site_spec.js b/core/test/regression/site/site_spec.js index 7050ceb5ed..941b192bc9 100644 --- a/core/test/regression/site/site_spec.js +++ b/core/test/regression/site/site_spec.js @@ -5397,6 +5397,54 @@ describe('Integration - Web - Site', function () { }); }); + describe('separate admin host w/ admin redirects disabled', function () { + before(function () { + testUtils.integrationTesting.urlService.resetGenerators(); + testUtils.integrationTesting.defaultMocks(sinon, {amp: true, apps: true}); + testUtils.integrationTesting.overrideGhostConfig(configUtils); + + configUtils.set('url', 'http://example.com'); + configUtils.set('admin:url', 'https://admin.example.com'); + configUtils.set('admin:redirects', false); + + return testUtils.integrationTesting.initGhost() + .then(function () { + sinon.stub(themeService.getActive(), 'engine').withArgs('ghost-api').returns('v2'); + sinon.stub(themeService.getActive(), 'config').withArgs('posts_per_page').returns(2); + + app = siteApp({start: true}); + return testUtils.integrationTesting.urlService.waitTillFinished(); + }) + .then(() => { + return appsService.init(); + }); + }); + + before(function () { + urlUtils.stubUrlUtilsFromConfig(); + }); + + after(function () { + configUtils.restore(); + urlUtils.restore(); + sinon.restore(); + }); + + it('does not redirect /ghost/ on configured url', function () { + const req = { + secure: false, + method: 'GET', + url: '/ghost/', + host: 'example.com' + }; + + return testUtils.mocks.express.invoke(app, req) + .then(function (response) { + response.statusCode.should.eql(404); + }); + }); + }); + describe('same host separate protocol', function () { before(function () { testUtils.integrationTesting.urlService.resetGenerators(); diff --git a/core/test/unit/services/routing/controllers/entry_spec.js b/core/test/unit/services/routing/controllers/entry_spec.js index 9998873f7c..67c5f3f3a8 100644 --- a/core/test/unit/services/routing/controllers/entry_spec.js +++ b/core/test/unit/services/routing/controllers/entry_spec.js @@ -1,11 +1,12 @@ -const should = require('should'), - sinon = require('sinon'), - testUtils = require('../../../../utils'), - urlService = require('../../../../../frontend/services/url'), - urlUtils = require('../../../../../server/lib/url-utils'), - controllers = require('../../../../../frontend/services/routing/controllers'), - helpers = require('../../../../../frontend/services/routing/helpers'), - EDITOR_URL = `/editor/post/`; +const should = require('should'); +const sinon = require('sinon'); +const testUtils = require('../../../../utils'); +const configUtils = require('../../../../utils/configUtils'); +const urlService = require('../../../../../frontend/services/url'); +const urlUtils = require('../../../../../server/lib/url-utils'); +const controllers = require('../../../../../frontend/services/routing/controllers'); +const helpers = require('../../../../../frontend/services/routing/helpers'); +const EDITOR_URL = `/editor/post/`; describe('Unit - services/routing/controllers/entry', function () { let req, res, entryLookUpStub, secureStub, renderStub, post, page; @@ -126,6 +127,30 @@ describe('Unit - services/routing/controllers/entry', function () { }); }); + it('isEditURL: true with admin redirects disabled', function (done) { + configUtils.set('admin:redirects', false); + + req.path = post.url; + + entryLookUpStub.withArgs(req.path, res.routerOptions) + .resolves({ + isEditURL: true, + entry: post + }); + + urlUtils.redirectToAdmin.callsFake(function (statusCode, res, editorUrl) { + configUtils.restore(); + done(new Error('redirectToAdmin was called')); + }); + + controllers.entry(req, res, (err) => { + configUtils.restore(); + urlUtils.redirectToAdmin.called.should.eql(false); + should.not.exist(err); + done(err); + }); + }); + it('type of router !== type of resource', function (done) { req.path = post.url; res.routerOptions.resourceType = 'posts';