From d42d112ebae310b1598fcc011faa017c6255d62e Mon Sep 17 00:00:00 2001 From: Rish Date: Tue, 19 Nov 2019 19:59:55 +0530 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B=20=20Fixed=20incorrect=20url=20gen?= =?UTF-8?q?eration=20for=20post=20reschedule/unschedule?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit no issue The default scheduling generates a known, independent URL for publishing a resource. In case of resource being rescheduled or unscheduled, the adapter expects the the same URL to remove/update existing jobs. The URL includes a JWT token for API auth which is calculated from post model and appended to URL. There was a bug in token generation which meant If we go to update or delete the job i.e. unschedule a post then a new token is used which means the existing scheduled job cannot be removed. This PR: - removes issued at (`iat`) timestamp from token generation which lead to a different token being generated for same payload - Fixes timestamp being used for URL calculation from resource model --- .../scheduling/post-scheduling/index.js | 30 +++++++++++-------- 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/core/server/adapters/scheduling/post-scheduling/index.js b/core/server/adapters/scheduling/post-scheduling/index.js index bf5618b98995..1fa6b1d5faad 100644 --- a/core/server/adapters/scheduling/post-scheduling/index.js +++ b/core/server/adapters/scheduling/post-scheduling/index.js @@ -32,21 +32,21 @@ _private.getSchedulerIntegration = function () { * * @return {Promise} */ -_private.getSignedAdminToken = function (options) { - const {model, apiUrl, integration} = options; +_private.getSignedAdminToken = function ({publishedAt, apiUrl, integration}) { let key = integration.api_keys[0]; const JWT_OPTIONS = { keyid: key.id, algorithm: 'HS256', - audience: apiUrl + audience: apiUrl, + noTimestamp: true }; // Default token expiry is till 6 hours after scheduled time // or if published_at is in past then till 6 hours after blog start // to allow for retries in case of network issues // and never before 10 mins to publish time - let tokenExpiry = moment(model.get('published_at')).add(6, 'h'); + let tokenExpiry = moment(publishedAt).add(6, 'h'); if (tokenExpiry.isBefore(moment())) { tokenExpiry = moment().add(6, 'h'); } @@ -54,7 +54,7 @@ _private.getSignedAdminToken = function (options) { return jwt.sign( { exp: tokenExpiry.unix(), - nbf: moment(model.get('published_at')).subtract(10, 'm').unix() + nbf: moment(publishedAt).subtract(10, 'm').unix() }, Buffer.from(key.secret, 'hex'), JWT_OPTIONS @@ -66,14 +66,14 @@ _private.getSignedAdminToken = function (options) { * @param {Object} options * @return {Object} */ -_private.normalize = function normalize(options) { - const {model, apiUrl, resourceType} = options; +_private.normalize = function normalize({model, apiUrl, resourceType, integration}, event = '') { const resource = `${resourceType}s`; - const signedAdminToken = _private.getSignedAdminToken(options); + let publishedAt = (event === 'unscheduled') ? model.previous('published_at') : model.get('published_at'); + const signedAdminToken = _private.getSignedAdminToken({publishedAt, apiUrl, integration}); let url = `${urlUtils.urlJoin(apiUrl, 'schedules', resource, model.get('id'))}/?token=${signedAdminToken}`; return { // NOTE: The scheduler expects a unix timestamp. - time: moment(model.get('published_at')).valueOf(), + time: moment(publishedAt).valueOf(), url: url, extra: { httpMethod: 'PUT', @@ -148,7 +148,8 @@ exports.init = function init(options = {}) { // and not an in-process implementation! Object.keys(scheduledResources).forEach((resourceType) => { scheduledResources[resourceType].forEach((model) => { - adapter.reschedule(_private.normalize({model, apiUrl, integration, resourceType}), {bootstrap: true}); + adapter.unschedule(_private.normalize({model, apiUrl, integration, resourceType}, 'unscheduled')); + adapter.schedule(_private.normalize({model, apiUrl, integration, resourceType})); }); }); }) @@ -161,12 +162,17 @@ exports.init = function init(options = {}) { adapter.schedule(_private.normalize({model, apiUrl, integration, resourceType: resource})); }); + /** We want to do reschedule as (unschedule + schedule) due to how token(+url) is generated + * We want to first remove existing schedule by generating a matching token(+url) + * followed by generating a new token(+url) for the new schedule + */ common.events.on(`${resource}.rescheduled`, (model) => { - adapter.reschedule(_private.normalize({model, apiUrl, integration, resourceType: resource})); + adapter.unschedule(_private.normalize({model, apiUrl, integration, resourceType: resource}, 'unscheduled')); + adapter.schedule(_private.normalize({model, apiUrl, integration, resourceType: resource})); }); common.events.on(`${resource}.unscheduled`, (model) => { - adapter.unschedule(_private.normalize({model, apiUrl, integration, resourceType: resource})); + adapter.unschedule(_private.normalize({model, apiUrl, integration, resourceType: resource}, 'unscheduled')); }); }); });