mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-03-11 02:12:21 -05:00
Blocked requests from integrations when integration limit is in place
https://github.com/TryGhost/Team/issues/599 - When custom integration limit is enabled all requests from existing integrations should not be accepted. With the exception of internal integrations like backup and scheduler
This commit is contained in:
parent
edd0e26a78
commit
cb0807d07a
4 changed files with 89 additions and 4 deletions
|
@ -2,6 +2,7 @@ const jwt = require('jsonwebtoken');
|
|||
const url = require('url');
|
||||
const models = require('../../../models');
|
||||
const errors = require('@tryghost/errors');
|
||||
const limitService = require('../../../services/limits');
|
||||
const {i18n} = require('../../../lib/common');
|
||||
const _ = require('lodash');
|
||||
|
||||
|
@ -98,7 +99,7 @@ const authenticateWithToken = async (req, res, next, {token, JWT_OPTIONS}) => {
|
|||
}
|
||||
|
||||
try {
|
||||
const apiKey = await models.ApiKey.findOne({id: apiKeyId});
|
||||
const apiKey = await models.ApiKey.findOne({id: apiKeyId}, {withRelated: ['integration']});
|
||||
|
||||
if (!apiKey) {
|
||||
return next(new errors.UnauthorizedError({
|
||||
|
@ -114,6 +115,14 @@ const authenticateWithToken = async (req, res, next, {token, JWT_OPTIONS}) => {
|
|||
}));
|
||||
}
|
||||
|
||||
// CASE: blocking all non-internal: "custom" and "builtin" integration requests when the limit is reached
|
||||
if (limitService.isLimited('customIntegrations')
|
||||
&& (apiKey.relations.integration && !['internal'].includes(apiKey.relations.integration.get('type')))) {
|
||||
// NOTE: using "checkWouldGoOverLimit" instead of "checkIsOverLimit" here because flag limits don't have
|
||||
// a concept of measuring if the limit has been surpassed
|
||||
await limitService.errorIfWouldGoOverLimit('customIntegrations');
|
||||
}
|
||||
|
||||
// Decoding from hex and transforming into bytes is here to
|
||||
// keep comparison of the bytes that are stored in the secret.
|
||||
// Useful context:
|
||||
|
@ -162,7 +171,11 @@ const authenticateWithToken = async (req, res, next, {token, JWT_OPTIONS}) => {
|
|||
|
||||
next();
|
||||
} catch (err) {
|
||||
next(new errors.InternalServerError({err}));
|
||||
if (err instanceof errors.HostLimitError) {
|
||||
next(err);
|
||||
} else {
|
||||
next(new errors.InternalServerError({err}));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
const models = require('../../../models');
|
||||
const errors = require('@tryghost/errors');
|
||||
const limitService = require('../../../services/limits');
|
||||
const {i18n} = require('../../../lib/common');
|
||||
|
||||
const authenticateContentApiKey = async function authenticateContentApiKey(req, res, next) {
|
||||
|
@ -18,7 +19,7 @@ const authenticateContentApiKey = async function authenticateContentApiKey(req,
|
|||
let key = req.query.key;
|
||||
|
||||
try {
|
||||
const apiKey = await models.ApiKey.findOne({secret: key});
|
||||
const apiKey = await models.ApiKey.findOne({secret: key}, {withRelated: ['integration']});
|
||||
|
||||
if (!apiKey) {
|
||||
return next(new errors.UnauthorizedError({
|
||||
|
@ -34,12 +35,24 @@ const authenticateContentApiKey = async function authenticateContentApiKey(req,
|
|||
}));
|
||||
}
|
||||
|
||||
// CASE: blocking all non-internal: "custom" and "builtin" integration requests when the limit is reached
|
||||
if (limitService.isLimited('customIntegrations')
|
||||
&& (apiKey.relations.integration && !['internal'].includes(apiKey.relations.integration.get('type')))) {
|
||||
// NOTE: using "checkWouldGoOverLimit" instead of "checkIsOverLimit" here because flag limits don't have
|
||||
// a concept of measuring if the limit has been surpassed
|
||||
await limitService.errorIfWouldGoOverLimit('customIntegrations');
|
||||
}
|
||||
|
||||
// authenticated OK, store the api key on the request for later checks and logging
|
||||
req.api_key = apiKey;
|
||||
|
||||
next();
|
||||
} catch (err) {
|
||||
next(new errors.InternalServerError({err}));
|
||||
if (err instanceof errors.HostLimitError) {
|
||||
next(err);
|
||||
} else {
|
||||
next(new errors.InternalServerError({err}));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@ const supertest = require('supertest');
|
|||
const testUtils = require('../../utils');
|
||||
const config = require('../../../core/shared/config');
|
||||
const localUtils = require('./utils');
|
||||
const configUtils = require('../../utils/configUtils');
|
||||
|
||||
describe('Admin API key authentication', function () {
|
||||
let request;
|
||||
|
@ -68,4 +69,32 @@ describe('Admin API key authentication', function () {
|
|||
|
||||
localUtils.API.checkResponse(res.body.users[0], 'user');
|
||||
});
|
||||
|
||||
describe('Host Settings: custom integration limits', function () {
|
||||
afterEach(function () {
|
||||
configUtils.set('hostSettings:limits', undefined);
|
||||
});
|
||||
|
||||
it('Blocks the request when host limit is in place for custom integrations', async function () {
|
||||
configUtils.set('hostSettings:limits', {
|
||||
customIntegrations: {
|
||||
disabled: true,
|
||||
error: 'Custom limit error message'
|
||||
}
|
||||
});
|
||||
|
||||
// NOTE: need to do a full reboot to reinitialize hostSettings
|
||||
await testUtils.startGhost();
|
||||
await testUtils.initFixtures('api_keys');
|
||||
|
||||
const response = await request.get(localUtils.API.getApiQuery('posts/'))
|
||||
.set('Authorization', `Ghost ${localUtils.getValidAdminToken('/canary/admin/')}`)
|
||||
.expect('Content-Type', /json/)
|
||||
.expect('Cache-Control', testUtils.cacheRules.private)
|
||||
.expect(403);
|
||||
|
||||
response.body.errors[0].type.should.equal('HostLimitError');
|
||||
response.body.errors[0].message.should.equal('Custom limit error message');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -3,6 +3,7 @@ const supertest = require('supertest');
|
|||
const testUtils = require('../../utils');
|
||||
const localUtils = require('./utils');
|
||||
const config = require('../../../core/shared/config');
|
||||
const configUtils = require('../../utils/configUtils');
|
||||
|
||||
describe('Content API key authentication', function () {
|
||||
let request;
|
||||
|
@ -28,4 +29,33 @@ describe('Content API key authentication', function () {
|
|||
.expect('Cache-Control', testUtils.cacheRules.private)
|
||||
.expect(200);
|
||||
});
|
||||
|
||||
describe('Host Settings: custom integration limits', function () {
|
||||
afterEach(function () {
|
||||
configUtils.set('hostSettings:limits', undefined);
|
||||
});
|
||||
|
||||
it('Blocks the request when host limit is in place for custom integrations', async function () {
|
||||
configUtils.set('hostSettings:limits', {
|
||||
customIntegrations: {
|
||||
disabled: true,
|
||||
error: 'Custom limit error message'
|
||||
}
|
||||
});
|
||||
|
||||
// NOTE: need to do a full reboot to reinitialize hostSettings
|
||||
await testUtils.startGhost();
|
||||
await testUtils.initFixtures('api_keys');
|
||||
|
||||
const key = localUtils.getValidKey();
|
||||
|
||||
const response = await request.get(localUtils.API.getApiQuery(`posts/?key=${key}`))
|
||||
.expect('Content-Type', /json/)
|
||||
.expect('Cache-Control', testUtils.cacheRules.private)
|
||||
.expect(403);
|
||||
|
||||
response.body.errors[0].errorType.should.equal('HostLimitError');
|
||||
response.body.errors[0].message.should.equal('Custom limit error message');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Add table
Reference in a new issue