mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-11-28 14:03:48 +03:00
Added new endpoint for refreshing api key secret (#11791)
no issue - Adds new endpoint on integration to refresh admin/content api key secret - Allows owner/admin to refresh their content or admin API keys for an integration via Ghost Admin - Adds a new `refreshed` event to actions table for anytime an api_key secret is refreshed
This commit is contained in:
parent
21f5912c2d
commit
a01bcdd2d0
@ -61,6 +61,7 @@ module.exports = {
|
||||
],
|
||||
options: [
|
||||
'id',
|
||||
'keyid',
|
||||
'include'
|
||||
],
|
||||
validation: {
|
||||
@ -74,6 +75,28 @@ module.exports = {
|
||||
}
|
||||
},
|
||||
query({data, options}) {
|
||||
if (options.keyid) {
|
||||
return models.ApiKey.findOne({id: options.keyid})
|
||||
.then(async (model) => {
|
||||
if (!model) {
|
||||
throw new common.errors.NotFoundError({
|
||||
message: common.i18n.t('errors.api.resource.resourceNotFound', {
|
||||
resource: 'ApiKey'
|
||||
})
|
||||
});
|
||||
}
|
||||
try {
|
||||
await models.ApiKey.refreshSecret(model.toJSON(), Object.assign({}, options, {id: options.keyid}));
|
||||
return models.Integration.findOne({id: options.id}, {
|
||||
withRelated: ['api_keys', 'webhooks']
|
||||
});
|
||||
} catch (err) {
|
||||
throw new common.errors.GhostError({
|
||||
err: err
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
return models.Integration.edit(data, Object.assign(options, {require: true}))
|
||||
.catch(models.Integration.NotFoundError, () => {
|
||||
throw new common.errors.NotFoundError({
|
||||
|
@ -1,4 +1,6 @@
|
||||
const omit = require('lodash/omit');
|
||||
const common = require('../lib/common');
|
||||
const _ = require('lodash');
|
||||
const crypto = require('crypto');
|
||||
const ghostBookshelf = require('./base');
|
||||
const {Role} = require('./role');
|
||||
@ -26,6 +28,50 @@ const createSecret = (type) => {
|
||||
return crypto.randomBytes(bytes).toString('hex');
|
||||
};
|
||||
|
||||
const addAction = (model, event, options) => {
|
||||
if (!model.wasChanged()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// CASE: model does not support actions at all
|
||||
if (!model.getAction) {
|
||||
return;
|
||||
}
|
||||
|
||||
const action = model.getAction(event, options);
|
||||
|
||||
// CASE: model does not support action for target event
|
||||
if (!action) {
|
||||
return;
|
||||
}
|
||||
|
||||
const insert = (action) => {
|
||||
ghostBookshelf.model('Action')
|
||||
.add(action)
|
||||
.catch((err) => {
|
||||
if (_.isArray(err)) {
|
||||
err = err[0];
|
||||
}
|
||||
|
||||
common.logging.error(new common.errors.InternalServerError({
|
||||
err
|
||||
}));
|
||||
});
|
||||
};
|
||||
|
||||
if (options.transacting) {
|
||||
options.transacting.once('committed', (committed) => {
|
||||
if (!committed) {
|
||||
return;
|
||||
}
|
||||
|
||||
insert(action);
|
||||
});
|
||||
} else {
|
||||
insert(action);
|
||||
}
|
||||
};
|
||||
|
||||
const ApiKey = ghostBookshelf.Model.extend({
|
||||
tableName: 'api_keys',
|
||||
|
||||
@ -67,6 +113,29 @@ const ApiKey = ghostBookshelf.Model.extend({
|
||||
this.set('role_id', null);
|
||||
}
|
||||
}
|
||||
},
|
||||
onUpdated(model, attrs, options) {
|
||||
if (this.previous('secret') !== this.get('secret')) {
|
||||
addAction(model, 'refreshed', options);
|
||||
}
|
||||
},
|
||||
|
||||
getAction(event, options) {
|
||||
const actor = this.getActor(options);
|
||||
|
||||
// @NOTE: we ignore internal updates (`options.context.internal`) for now
|
||||
if (!actor) {
|
||||
return;
|
||||
}
|
||||
|
||||
// @TODO: implement context
|
||||
return {
|
||||
event: event,
|
||||
resource_id: this.id || this.previous('id'),
|
||||
resource_type: 'api_key',
|
||||
actor_id: actor.id,
|
||||
actor_type: actor.type
|
||||
};
|
||||
}
|
||||
}, {
|
||||
refreshSecret(data, options) {
|
||||
|
@ -42,6 +42,7 @@ module.exports = function apiRoutes() {
|
||||
router.get('/integrations', mw.authAdminApi, http(apiCanary.integrations.browse));
|
||||
router.get('/integrations/:id', mw.authAdminApi, http(apiCanary.integrations.read));
|
||||
router.post('/integrations', mw.authAdminApi, http(apiCanary.integrations.add));
|
||||
router.post('/integrations/:id/api_key/:keyid/refresh', mw.authAdminApi, http(apiCanary.integrations.edit));
|
||||
router.put('/integrations/:id', mw.authAdminApi, http(apiCanary.integrations.edit));
|
||||
router.del('/integrations/:id', mw.authAdminApi, http(apiCanary.integrations.destroy));
|
||||
|
||||
|
@ -269,6 +269,67 @@ describe('Integrations API', function () {
|
||||
});
|
||||
});
|
||||
|
||||
it('Can successfully refresh an integration api key', function (done) {
|
||||
request.post(localUtils.API.getApiQuery('integrations/?include=api_keys'))
|
||||
.set('Origin', config.get('url'))
|
||||
.send({
|
||||
integrations: [{
|
||||
name: 'Rubbish Integration Name'
|
||||
}]
|
||||
})
|
||||
.expect(201)
|
||||
.end(function (err, {body}) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
const [createdIntegration] = body.integrations;
|
||||
const apiKeys = createdIntegration.api_keys;
|
||||
const adminApiKey = apiKeys.find(key => key.type === 'admin');
|
||||
request.post(localUtils.API.getApiQuery(`integrations/${createdIntegration.id}/api_key/${adminApiKey.id}/refresh`))
|
||||
.set('Origin', config.get('url'))
|
||||
.send({
|
||||
integrations: [{
|
||||
id: createdIntegration.id
|
||||
}]
|
||||
})
|
||||
.expect(200)
|
||||
.end(function (err) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
|
||||
request.get(localUtils.API.getApiQuery(`integrations/${createdIntegration.id}/?include=api_keys`))
|
||||
.set('Origin', config.get('url'))
|
||||
.expect('Content-Type', /json/)
|
||||
.expect('Cache-Control', testUtils.cacheRules.private)
|
||||
.expect(200)
|
||||
.end(function (err, {body}) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
|
||||
const [updatedIntegration] = body.integrations;
|
||||
const updatedAdminApiKey = updatedIntegration.api_keys.find(key => key.type === 'admin');
|
||||
should.equal(updatedIntegration.id, createdIntegration.id);
|
||||
updatedAdminApiKey.secret.should.not.eql(adminApiKey.secret);
|
||||
request.get(localUtils.API.getApiQuery(`actions/?filter=resource_id:${adminApiKey.id}&include=actor`))
|
||||
.set('Origin', config.get('url'))
|
||||
.expect('Content-Type', /json/)
|
||||
.expect('Cache-Control', testUtils.cacheRules.private)
|
||||
.expect(200)
|
||||
.end(function (err, {body}) {
|
||||
const actions = body.actions;
|
||||
const refreshedAction = actions.find((action) => {
|
||||
return action.event === 'refreshed';
|
||||
});
|
||||
should.exist(refreshedAction);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('Can successfully add and delete a created integrations webhooks', function (done) {
|
||||
request.post(localUtils.API.getApiQuery('integrations/'))
|
||||
.set('Origin', config.get('url'))
|
||||
|
Loading…
Reference in New Issue
Block a user