392 lines
12 KiB
JavaScript
392 lines
12 KiB
JavaScript
describe('rate limit', () => {
|
|
it('can limit cloud functions', async () => {
|
|
Parse.Cloud.define('test', () => 'Abc');
|
|
await reconfigureServer({
|
|
rateLimit: [
|
|
{
|
|
requestPath: '/functions/*',
|
|
requestTimeWindow: 10000,
|
|
requestCount: 1,
|
|
errorResponseMessage: 'Too many requests',
|
|
includeInternalRequests: true,
|
|
},
|
|
],
|
|
});
|
|
const response1 = await Parse.Cloud.run('test');
|
|
expect(response1).toBe('Abc');
|
|
await expectAsync(Parse.Cloud.run('test')).toBeRejectedWith(
|
|
new Parse.Error(Parse.Error.CONNECTION_FAILED, 'Too many requests')
|
|
);
|
|
});
|
|
|
|
it('can limit cloud functions with user session token', async () => {
|
|
await Parse.User.signUp('myUser', 'password');
|
|
Parse.Cloud.define('test', () => 'Abc');
|
|
await reconfigureServer({
|
|
rateLimit: [
|
|
{
|
|
requestPath: '/functions/*',
|
|
requestTimeWindow: 10000,
|
|
requestCount: 1,
|
|
errorResponseMessage: 'Too many requests',
|
|
includeInternalRequests: true,
|
|
},
|
|
],
|
|
});
|
|
const response1 = await Parse.Cloud.run('test');
|
|
expect(response1).toBe('Abc');
|
|
await expectAsync(Parse.Cloud.run('test')).toBeRejectedWith(
|
|
new Parse.Error(Parse.Error.CONNECTION_FAILED, 'Too many requests')
|
|
);
|
|
});
|
|
|
|
it('can add global limit', async () => {
|
|
Parse.Cloud.define('test', () => 'Abc');
|
|
await reconfigureServer({
|
|
rateLimit: {
|
|
requestPath: '*',
|
|
requestTimeWindow: 10000,
|
|
requestCount: 1,
|
|
errorResponseMessage: 'Too many requests',
|
|
includeInternalRequests: true,
|
|
},
|
|
});
|
|
const response1 = await Parse.Cloud.run('test');
|
|
expect(response1).toBe('Abc');
|
|
await expectAsync(Parse.Cloud.run('test')).toBeRejectedWith(
|
|
new Parse.Error(Parse.Error.CONNECTION_FAILED, 'Too many requests')
|
|
);
|
|
await expectAsync(new Parse.Object('Test').save()).toBeRejectedWith(
|
|
new Parse.Error(Parse.Error.CONNECTION_FAILED, 'Too many requests')
|
|
);
|
|
});
|
|
|
|
it('can limit cloud with validator', async () => {
|
|
Parse.Cloud.define('test', () => 'Abc', {
|
|
rateLimit: {
|
|
requestTimeWindow: 10000,
|
|
requestCount: 1,
|
|
errorResponseMessage: 'Too many requests',
|
|
includeInternalRequests: true,
|
|
},
|
|
});
|
|
const response1 = await Parse.Cloud.run('test');
|
|
expect(response1).toBe('Abc');
|
|
await expectAsync(Parse.Cloud.run('test')).toBeRejectedWith(
|
|
new Parse.Error(Parse.Error.CONNECTION_FAILED, 'Too many requests')
|
|
);
|
|
});
|
|
|
|
it('can skip with masterKey', async () => {
|
|
Parse.Cloud.define('test', () => 'Abc');
|
|
await reconfigureServer({
|
|
rateLimit: [
|
|
{
|
|
requestPath: '/functions/*',
|
|
requestTimeWindow: 10000,
|
|
requestCount: 1,
|
|
errorResponseMessage: 'Too many requests',
|
|
includeInternalRequests: true,
|
|
},
|
|
],
|
|
});
|
|
const response1 = await Parse.Cloud.run('test', null, { useMasterKey: true });
|
|
expect(response1).toBe('Abc');
|
|
const response2 = await Parse.Cloud.run('test', null, { useMasterKey: true });
|
|
expect(response2).toBe('Abc');
|
|
});
|
|
|
|
it('should run with masterKey', async () => {
|
|
Parse.Cloud.define('test', () => 'Abc');
|
|
await reconfigureServer({
|
|
rateLimit: [
|
|
{
|
|
requestPath: '/functions/*',
|
|
requestTimeWindow: 10000,
|
|
requestCount: 1,
|
|
includeMasterKey: true,
|
|
errorResponseMessage: 'Too many requests',
|
|
includeInternalRequests: true,
|
|
},
|
|
],
|
|
});
|
|
const response1 = await Parse.Cloud.run('test', null, { useMasterKey: true });
|
|
expect(response1).toBe('Abc');
|
|
await expectAsync(Parse.Cloud.run('test')).toBeRejectedWith(
|
|
new Parse.Error(Parse.Error.CONNECTION_FAILED, 'Too many requests')
|
|
);
|
|
});
|
|
|
|
it('can limit saving objects', async () => {
|
|
await reconfigureServer({
|
|
rateLimit: [
|
|
{
|
|
requestPath: '/classes/*',
|
|
requestTimeWindow: 10000,
|
|
requestCount: 1,
|
|
errorResponseMessage: 'Too many requests',
|
|
includeInternalRequests: true,
|
|
},
|
|
],
|
|
});
|
|
const obj = new Parse.Object('Test');
|
|
await obj.save();
|
|
await expectAsync(obj.save()).toBeRejectedWith(
|
|
new Parse.Error(Parse.Error.CONNECTION_FAILED, 'Too many requests')
|
|
);
|
|
});
|
|
|
|
it('can set method to post', async () => {
|
|
await reconfigureServer({
|
|
rateLimit: [
|
|
{
|
|
requestPath: '/classes/*',
|
|
requestTimeWindow: 10000,
|
|
requestCount: 1,
|
|
requestMethods: 'POST',
|
|
errorResponseMessage: 'Too many requests',
|
|
includeInternalRequests: true,
|
|
},
|
|
],
|
|
});
|
|
const obj = new Parse.Object('Test');
|
|
await obj.save();
|
|
await obj.save();
|
|
const obj2 = new Parse.Object('Test');
|
|
await expectAsync(obj2.save()).toBeRejectedWith(
|
|
new Parse.Error(Parse.Error.CONNECTION_FAILED, 'Too many requests')
|
|
);
|
|
});
|
|
|
|
it('can use a validator for post', async () => {
|
|
Parse.Cloud.beforeSave('Test', () => {}, {
|
|
rateLimit: {
|
|
requestTimeWindow: 10000,
|
|
requestCount: 1,
|
|
errorResponseMessage: 'Too many requests',
|
|
includeInternalRequests: true,
|
|
},
|
|
});
|
|
const obj = new Parse.Object('Test');
|
|
await obj.save();
|
|
await expectAsync(obj.save()).toBeRejectedWith(
|
|
new Parse.Error(Parse.Error.CONNECTION_FAILED, 'Too many requests')
|
|
);
|
|
});
|
|
|
|
it('can use a validator for file', async () => {
|
|
Parse.Cloud.beforeSave(Parse.File, () => {}, {
|
|
rateLimit: {
|
|
requestTimeWindow: 10000,
|
|
requestCount: 1,
|
|
errorResponseMessage: 'Too many requests',
|
|
includeInternalRequests: true,
|
|
},
|
|
});
|
|
const file = new Parse.File('yolo.txt', [1, 2, 3], 'text/plain');
|
|
await file.save();
|
|
const file2 = new Parse.File('yolo.txt', [1, 2, 3], 'text/plain');
|
|
await expectAsync(file2.save()).toBeRejectedWith(
|
|
new Parse.Error(Parse.Error.CONNECTION_FAILED, 'Too many requests')
|
|
);
|
|
});
|
|
|
|
it('can set method to get', async () => {
|
|
await reconfigureServer({
|
|
rateLimit: [
|
|
{
|
|
requestPath: '/classes/Test',
|
|
requestTimeWindow: 10000,
|
|
requestCount: 1,
|
|
requestMethods: 'GET',
|
|
errorResponseMessage: 'Too many requests',
|
|
includeInternalRequests: true,
|
|
},
|
|
],
|
|
});
|
|
const obj = new Parse.Object('Test');
|
|
await obj.save();
|
|
await obj.save();
|
|
await new Parse.Query('Test').first();
|
|
await expectAsync(new Parse.Query('Test').first()).toBeRejectedWith(
|
|
new Parse.Error(Parse.Error.CONNECTION_FAILED, 'Too many requests')
|
|
);
|
|
});
|
|
|
|
it('can use a validator', async () => {
|
|
await reconfigureServer({ silent: false });
|
|
Parse.Cloud.beforeFind('TestObject', () => {}, {
|
|
rateLimit: {
|
|
requestTimeWindow: 10000,
|
|
requestCount: 1,
|
|
errorResponseMessage: 'Too many requests',
|
|
includeInternalRequests: true,
|
|
},
|
|
});
|
|
const obj = new Parse.Object('TestObject');
|
|
await obj.save();
|
|
await obj.save();
|
|
await new Parse.Query('TestObject').first();
|
|
await expectAsync(new Parse.Query('TestObject').first()).toBeRejectedWith(
|
|
new Parse.Error(Parse.Error.CONNECTION_FAILED, 'Too many requests')
|
|
);
|
|
await expectAsync(new Parse.Query('TestObject').get('abc')).toBeRejectedWith(
|
|
new Parse.Error(Parse.Error.CONNECTION_FAILED, 'Too many requests')
|
|
);
|
|
});
|
|
|
|
it('can set method to delete', async () => {
|
|
await reconfigureServer({
|
|
rateLimit: [
|
|
{
|
|
requestPath: '/classes/Test/*',
|
|
requestTimeWindow: 10000,
|
|
requestCount: 1,
|
|
requestMethods: 'DELETE',
|
|
errorResponseMessage: 'Too many requests',
|
|
includeInternalRequests: true,
|
|
},
|
|
],
|
|
});
|
|
const obj = new Parse.Object('Test');
|
|
await obj.save();
|
|
await obj.destroy();
|
|
await expectAsync(obj.destroy()).toBeRejectedWith(
|
|
new Parse.Error(Parse.Error.CONNECTION_FAILED, 'Too many requests')
|
|
);
|
|
});
|
|
|
|
it('can set beforeDelete', async () => {
|
|
const obj = new Parse.Object('TestDelete');
|
|
await obj.save();
|
|
Parse.Cloud.beforeDelete('TestDelete', () => {}, {
|
|
rateLimit: {
|
|
requestTimeWindow: 10000,
|
|
requestCount: 1,
|
|
errorResponseMessage: 'Too many requests',
|
|
includeInternalRequests: true,
|
|
},
|
|
});
|
|
await obj.destroy();
|
|
await expectAsync(obj.destroy()).toBeRejectedWith(
|
|
new Parse.Error(Parse.Error.CONNECTION_FAILED, 'Too many requests')
|
|
);
|
|
});
|
|
|
|
it('can set beforeLogin', async () => {
|
|
Parse.Cloud.beforeLogin(() => {}, {
|
|
rateLimit: {
|
|
requestTimeWindow: 10000,
|
|
requestCount: 1,
|
|
errorResponseMessage: 'Too many requests',
|
|
includeInternalRequests: true,
|
|
},
|
|
});
|
|
await Parse.User.signUp('myUser', 'password');
|
|
await Parse.User.logIn('myUser', 'password');
|
|
await expectAsync(Parse.User.logIn('myUser', 'password')).toBeRejectedWith(
|
|
new Parse.Error(Parse.Error.CONNECTION_FAILED, 'Too many requests')
|
|
);
|
|
});
|
|
|
|
it('can define limits via rateLimit and define', async () => {
|
|
await reconfigureServer({
|
|
rateLimit: [
|
|
{
|
|
requestPath: '/functions/*',
|
|
requestTimeWindow: 10000,
|
|
requestCount: 100,
|
|
errorResponseMessage: 'Too many requests',
|
|
includeInternalRequests: true,
|
|
},
|
|
],
|
|
});
|
|
Parse.Cloud.define('test', () => 'Abc', {
|
|
rateLimit: {
|
|
requestTimeWindow: 10000,
|
|
requestCount: 1,
|
|
includeInternalRequests: true,
|
|
},
|
|
});
|
|
const response1 = await Parse.Cloud.run('test');
|
|
expect(response1).toBe('Abc');
|
|
await expectAsync(Parse.Cloud.run('test')).toBeRejectedWith(
|
|
new Parse.Error(Parse.Error.CONNECTION_FAILED, 'Too many requests.')
|
|
);
|
|
});
|
|
|
|
it('does not limit internal calls', async () => {
|
|
await reconfigureServer({
|
|
rateLimit: [
|
|
{
|
|
requestPath: '/functions/*',
|
|
requestTimeWindow: 10000,
|
|
requestCount: 1,
|
|
errorResponseMessage: 'Too many requests',
|
|
},
|
|
],
|
|
});
|
|
Parse.Cloud.define('test1', () => 'Abc');
|
|
Parse.Cloud.define('test2', async () => {
|
|
await Parse.Cloud.run('test1');
|
|
await Parse.Cloud.run('test1');
|
|
});
|
|
await Parse.Cloud.run('test2');
|
|
});
|
|
|
|
it('can validate rateLimit', async () => {
|
|
const Config = require('../lib/Config');
|
|
const validateRateLimit = ({ rateLimit }) => Config.validateRateLimit(rateLimit);
|
|
expect(() =>
|
|
validateRateLimit({ rateLimit: 'a', requestTimeWindow: 1000, requestCount: 3 })
|
|
).toThrow('rateLimit must be an array or object');
|
|
expect(() => validateRateLimit({ rateLimit: ['a'] })).toThrow(
|
|
'rateLimit must be an array of objects'
|
|
);
|
|
expect(() => validateRateLimit({ rateLimit: [{ requestPath: [] }] })).toThrow(
|
|
'rateLimit.requestPath must be a string'
|
|
);
|
|
expect(() =>
|
|
validateRateLimit({ rateLimit: [{ requestTimeWindow: [], requestPath: 'a' }] })
|
|
).toThrow('rateLimit.requestTimeWindow must be a number');
|
|
expect(() =>
|
|
validateRateLimit({
|
|
rateLimit: [
|
|
{
|
|
includeInternalRequests: [],
|
|
requestTimeWindow: 1000,
|
|
requestCount: 3,
|
|
requestPath: 'a',
|
|
},
|
|
],
|
|
})
|
|
).toThrow('rateLimit.includeInternalRequests must be a boolean');
|
|
expect(() =>
|
|
validateRateLimit({
|
|
rateLimit: [{ requestCount: [], requestTimeWindow: 1000, requestPath: 'a' }],
|
|
})
|
|
).toThrow('rateLimit.requestCount must be a number');
|
|
expect(() =>
|
|
validateRateLimit({
|
|
rateLimit: [
|
|
{ errorResponseMessage: [], requestTimeWindow: 1000, requestCount: 3, requestPath: 'a' },
|
|
],
|
|
})
|
|
).toThrow('rateLimit.errorResponseMessage must be a string');
|
|
expect(() =>
|
|
validateRateLimit({ rateLimit: [{ requestCount: 3, requestPath: 'abc' }] })
|
|
).toThrow('rateLimit.requestTimeWindow must be defined');
|
|
expect(() =>
|
|
validateRateLimit({ rateLimit: [{ requestTimeWindow: 3, requestPath: 'abc' }] })
|
|
).toThrow('rateLimit.requestCount must be defined');
|
|
expect(() =>
|
|
validateRateLimit({ rateLimit: [{ requestTimeWindow: 3, requestCount: 'abc' }] })
|
|
).toThrow('rateLimit.requestPath must be defined');
|
|
await expectAsync(
|
|
reconfigureServer({
|
|
rateLimit: [{ requestTimeWindow: 3, requestCount: 1, path: 'abc', requestPath: 'a' }],
|
|
})
|
|
).toBeRejectedWith(`Invalid rate limit option "path"`);
|
|
});
|
|
});
|