diff --git a/core/server/services/api-version-compatibility/index.js b/core/server/services/api-version-compatibility/index.js index f03cdb5cd1..5259b79406 100644 --- a/core/server/services/api-version-compatibility/index.js +++ b/core/server/services/api-version-compatibility/index.js @@ -49,5 +49,6 @@ module.exports.contentVersion = (req, res, next) => { }; module.exports.versionRewrites = require('./mw-version-rewrites'); +module.exports.legacyApiPathMatch = require('./legacy-api-path-match'); module.exports.init = init; diff --git a/core/server/services/api-version-compatibility/legacy-api-path-match.js b/core/server/services/api-version-compatibility/legacy-api-path-match.js new file mode 100644 index 0000000000..137912185a --- /dev/null +++ b/core/server/services/api-version-compatibility/legacy-api-path-match.js @@ -0,0 +1,21 @@ +const pathMatch = require('path-match')(); + +module.exports = (url) => { + let apiRouteMatcher = '/:version(v2|v3|v4|canary)?/:api(admin|content)/*'; + + if (url.startsWith('/ghost/api')) { + apiRouteMatcher = `/ghost/api${apiRouteMatcher}`; + } + + if (!url.endsWith('/')) { + url += '/'; + } + + let {version, api} = pathMatch(apiRouteMatcher)(url); + + if (version === [null]) { + version = null; + } + + return {version, api}; +}; diff --git a/core/server/services/api-version-compatibility/mw-version-rewrites.js b/core/server/services/api-version-compatibility/mw-version-rewrites.js index 7d92910d54..351378c7b9 100644 --- a/core/server/services/api-version-compatibility/mw-version-rewrites.js +++ b/core/server/services/api-version-compatibility/mw-version-rewrites.js @@ -1,4 +1,4 @@ -const routeMatch = require('path-match')(); +const legacyApiPathMatch = require('./legacy-api-path-match'); const urlUtils = require('../../../shared/url-utils'); /** @@ -9,7 +9,7 @@ const urlUtils = require('../../../shared/url-utils'); * @param {import('express').NextFunction} next */ module.exports = (req, res, next) => { - let {version} = routeMatch('/:version(v2|v3|v4|canary)/:api(admin|content)/*')(req.url); + let {version} = legacyApiPathMatch(req.url); // If we don't match a valid version, carry on if (!version) { diff --git a/core/server/services/auth/api-key/admin.js b/core/server/services/auth/api-key/admin.js index 708a5e10af..64936e61c3 100644 --- a/core/server/services/auth/api-key/admin.js +++ b/core/server/services/auth/api-key/admin.js @@ -3,7 +3,7 @@ const url = require('url'); const models = require('../../../models'); const errors = require('@tryghost/errors'); const limitService = require('../../../services/limits'); -const config = require('../../../../shared/config'); +const {legacyApiPathMatch} = require('../../../services/api-version-compatibility'); const tpl = require('@tryghost/tpl'); const _ = require('lodash'); @@ -139,19 +139,20 @@ const authenticateWithToken = async (req, res, next, {token, JWT_OPTIONS}) => { // https://github.com/auth0/node-jsonwebtoken/issues/208#issuecomment-231861138 const secret = Buffer.from(apiKey.get('secret'), 'hex'); - const {pathname} = url.parse(req.originalUrl); - const [hasMatch, version, api] = pathname.match(/ghost\/api\/([^/]+)\/([^/]+)\/(.+)*/); // eslint-disable-line no-unused-vars + // Using req.originalUril means we get the right url even if version-rewrites have happened + const {version, api} = legacyApiPathMatch(req.originalUrl); // ensure the token was meant for this api let options; - if (!config.get('api:versions:all').includes(version)) { - // CASE: non-versioned api request + + if (version) { + // CASE: legacy versioned api request options = Object.assign({ - audience: new RegExp(`\/?${version}\/?$`) // eslint-disable-line no-useless-escape + audience: new RegExp(`/?${version}/${api}/?$`) }, JWT_OPTIONS); } else { options = Object.assign({ - audience: new RegExp(`\/?${version}\/${api}\/?$`) // eslint-disable-line no-useless-escape + audience: new RegExp(`/?${api}/?$`) }, JWT_OPTIONS); } diff --git a/test/unit/server/services/api-version-compatibility/legacy-api-path-match.test.js b/test/unit/server/services/api-version-compatibility/legacy-api-path-match.test.js new file mode 100644 index 0000000000..b95271362b --- /dev/null +++ b/test/unit/server/services/api-version-compatibility/legacy-api-path-match.test.js @@ -0,0 +1,68 @@ +const assert = require('assert'); + +const legacyApiPathMatch = require('../../../../../core/server/services/api-version-compatibility/legacy-api-path-match'); + +describe('Legacy Path Match', function () { + it('returns null, admin for all supported permutations', function () { + const permutations = [ + '/ghost/api/admin/', + '/admin/', + '/ghost/api/admin', + '/admin', + '/ghost/api/admin/session/', + '/admin/session/', + '/ghost/api/admin/session', + '/admin/session', + '/ghost/api/admin/session/something/', + '/admin/session/something/', + '/ghost/api/admin/session/something', + '/admin/session/something' + ]; + + permutations.forEach((url) => { + assert.deepEqual(legacyApiPathMatch(url), {version: null, api: 'admin'}, url); + }); + }); + + it('returns canary, admin for all supported permutations', function () { + const permutations = [ + '/ghost/api/canary/admin/', + '/canary/admin/', + '/ghost/api/canary/admin', + '/canary/admin', + '/ghost/api/canary/admin/session/', + '/canary/admin/session/', + '/ghost/api/canary/admin/session', + '/canary/admin/session', + '/ghost/api/canary/admin/session/something/', + '/canary/admin/session/something/', + '/ghost/api/canary/admin/session/something', + '/canary/admin/session/something' + ]; + + permutations.forEach((url) => { + assert.deepEqual(legacyApiPathMatch(url), {version: 'canary', api: 'admin'}, url); + }); + }); + + it('returns v4, admin for all permutations', function () { + const permutations = [ + '/ghost/api/v4/admin/', + '/v4/admin/', + '/ghost/api/v4/admin', + '/v4/admin', + '/ghost/api/v4/admin/session/', + '/v4/admin/session/', + '/ghost/api/v4/admin/session', + '/v4/admin/session', + '/ghost/api/v4/admin/session/something/', + '/v4/admin/session/something/', + '/ghost/api/v4/admin/session/something', + '/v4/admin/session/something' + ]; + + permutations.forEach((url) => { + assert.deepEqual(legacyApiPathMatch(url), {version: 'v4', api: 'admin'}, url); + }); + }); +});