diff --git a/ghost/limit-service/lib/limit-service.js b/ghost/limit-service/lib/limit-service.js index 76bd3f5c91..bd8652c867 100644 --- a/ghost/limit-service/lib/limit-service.js +++ b/ghost/limit-service/lib/limit-service.js @@ -1,4 +1,4 @@ -const {MaxLimit, FlagLimit} = require('./limit'); +const {MaxLimit, FlagLimit, AllowlistLimit} = require('./limit'); const config = require('./config'); const _ = require('lodash'); @@ -30,7 +30,9 @@ class LimitService { /** @type LimitConfig */ let limitConfig = Object.assign({}, config[name], limits[name]); - if (_.has(limitConfig, 'max')) { + if (_.has(limitConfig, 'allowlist')) { + this.limits[name] = new AllowlistLimit({name, config: limitConfig, helpLink, errors}); + } else if (_.has(limitConfig, 'max')) { this.limits[name] = new MaxLimit({name: name, config: limitConfig, helpLink, db, errors}); } else { this.limits[name] = new FlagLimit({name: name, config: limitConfig, helpLink, errors}); @@ -58,13 +60,13 @@ class LimitService { } } - async checkWouldGoOverLimit(limitName) { + async checkWouldGoOverLimit(limitName, metadata = {}) { if (!this.isLimited(limitName)) { return; } try { - await this.limits[limitName].errorIfWouldGoOverLimit(); + await this.limits[limitName].errorIfWouldGoOverLimit(metadata); return false; } catch (error) { if (error instanceof this.errors.HostLimitError) { @@ -73,20 +75,20 @@ class LimitService { } } - async errorIfIsOverLimit(limitName) { + async errorIfIsOverLimit(limitName, metadata = {}) { if (!this.isLimited(limitName)) { return; } - await this.limits[limitName].errorIfIsOverLimit(); + await this.limits[limitName].errorIfIsOverLimit(metadata); } - async errorIfWouldGoOverLimit(limitName) { + async errorIfWouldGoOverLimit(limitName, metadata = {}) { if (!this.isLimited(limitName)) { return; } - await this.limits[limitName].errorIfWouldGoOverLimit(); + await this.limits[limitName].errorIfWouldGoOverLimit(metadata); } } diff --git a/ghost/limit-service/lib/limit.js b/ghost/limit-service/lib/limit.js index 034a373bd1..18ae7701ae 100644 --- a/ghost/limit-service/lib/limit.js +++ b/ghost/limit-service/lib/limit.js @@ -157,7 +157,51 @@ class FlagLimit extends Limit { } } +class AllowlistLimit extends Limit { + constructor({name, config, helpLink, errors}) { + super({name, error: config.error || '', helpLink, errors}); + + if (!config.allowlist || !config.allowlist.length) { + throw new this.errors.IncorrectUsageError('Attempted to setup an allowlist limit without an allowlist'); + } + + this.allowlist = config.allowlist; + this.fallbackMessage = `This action would exceed the ${_.lowerCase(this.name)} limit on your current plan.`; + } + + generateError() { + let errorObj = super.generateError(); + + if (this.error) { + errorObj.message = this.error; + } else { + errorObj.message = this.fallbackMessage; + } + + return new this.errors.HostLimitError(errorObj); + } + + async errorIfWouldGoOverLimit(metadata) { + if (!metadata.value) { + throw new this.errors.IncorrectUsageError('Attempted to check an allowlist limit without a value'); + } + if (!this.allowlist.includes(metadata.value)) { + throw this.generateError(); + } + } + + async errorIfIsOverLimit(metadata) { + if (!metadata.value) { + throw new this.errors.IncorrectUsageError('Attempted to check an allowlist limit without a value'); + } + if (!this.allowlist.includes(metadata.value)) { + throw this.generateError(); + } + } +} + module.exports = { MaxLimit, - FlagLimit + FlagLimit, + AllowlistLimit }; diff --git a/ghost/limit-service/test/limit.test.js b/ghost/limit-service/test/limit.test.js index 9e5629d15f..1a334a0fea 100644 --- a/ghost/limit-service/test/limit.test.js +++ b/ghost/limit-service/test/limit.test.js @@ -3,7 +3,7 @@ require('./utils'); const errors = require('./fixtures/errors'); -const {MaxLimit} = require('../lib/limit'); +const {MaxLimit, AllowlistLimit} = require('../lib/limit'); describe('Limit Service', function () { describe('Max Limit', function () { @@ -167,4 +167,36 @@ describe('Limit Service', function () { }); }); }); + + describe('Allowlist limit', function () { + it('rejects when the allowlist config isn\'t specified', async function () { + try { + new AllowlistLimit({name: 'test', config: {}, errors}); + throw new Error('Should have failed earlier...'); + } catch (error) { + error.errorType.should.equal('IncorrectUsageError'); + } + }); + + it('accept correct values', async function () { + const limit = new AllowlistLimit({name: 'test', config: { + allowlist: ['test', 'ok'] + }, errors}); + + await limit.errorIfIsOverLimit({value: 'test'}); + }); + + it('rejects unkown values', async function () { + const limit = new AllowlistLimit({name: 'test', config: { + allowlist: ['test', 'ok'] + }, errors}); + + try { + await limit.errorIfIsOverLimit({value: 'unkown value'}); + throw new Error('Should have failed earlier...'); + } catch (error) { + error.errorType.should.equal('HostLimitError'); + } + }); + }); });