diff --git a/server/api/middlewares/pagination.js b/server/api/middlewares/pagination.js index f63daf1aa..ea2996156 100644 --- a/server/api/middlewares/pagination.js +++ b/server/api/middlewares/pagination.js @@ -10,24 +10,39 @@ export default function pagination(options?: Object) { ) { const opts = { defaultLimit: 15, + defaultOffset: 0, maxLimit: 100, ...options, }; let query = ctx.request.query; - let body = ctx.request.body; - // $FlowFixMe - let limit = parseInt(query.limit || body.limit, 10); - // $FlowFixMe - let offset = parseInt(query.offset || body.offset, 10); - limit = isNaN(limit) ? opts.defaultLimit : limit; - offset = isNaN(offset) ? 0 : offset; + let body: Object = ctx.request.body; + let limit = query.limit || body.limit; + let offset = query.offset || body.offset; + + if (limit && isNaN(limit)) { + throw new InvalidRequestError(`Pagination limit must be a valid number`); + } + if (offset && isNaN(offset)) { + throw new InvalidRequestError(`Pagination offset must be a valid number`); + } + + limit = parseInt(limit || opts.defaultLimit, 10); + offset = parseInt(offset || opts.defaultOffset, 10); if (limit > opts.maxLimit) { throw new InvalidRequestError( `Pagination limit is too large (max ${opts.maxLimit})` ); } + if (limit <= 0) { + throw new InvalidRequestError(`Pagination limit must be greater than 0`); + } + if (offset < 0) { + throw new InvalidRequestError( + `Pagination offset must be greater than or equal to 0` + ); + } ctx.state.pagination = { limit: limit, diff --git a/server/api/middlewares/pagination.test.js b/server/api/middlewares/pagination.test.js new file mode 100644 index 000000000..fb61fca72 --- /dev/null +++ b/server/api/middlewares/pagination.test.js @@ -0,0 +1,56 @@ +/* eslint-disable flowtype/require-valid-file-annotation */ +import TestServer from 'fetch-test-server'; +import app from '../../app'; +import { flushdb, seed } from '../../test/support'; + +const server = new TestServer(app.callback()); + +beforeEach(flushdb); +afterAll(server.close); + +describe('#pagination', async () => { + it('should allow offset and limit', async () => { + const { user } = await seed(); + const res = await server.post('/api/users.list', { + body: { token: user.getJwtToken(), limit: 1, offset: 1 }, + }); + + expect(res.status).toEqual(200); + }); + + it('should not allow negative limit', async () => { + const { user } = await seed(); + const res = await server.post('/api/users.list', { + body: { token: user.getJwtToken(), limit: -1 }, + }); + + expect(res.status).toEqual(400); + }); + + it('should not allow non-integer limit', async () => { + const { user } = await seed(); + const res = await server.post('/api/users.list', { + body: { token: user.getJwtToken(), limit: 'blah' }, + }); + + expect(res.status).toEqual(400); + }); + + it('should not allow negative offset', async () => { + const { user } = await seed(); + const res = await server.post('/api/users.list', { + body: { token: user.getJwtToken(), offset: -1 }, + }); + + expect(res.status).toEqual(400); + }); + + it('should not allow non-integer offset', async () => { + const { user } = await seed(); + const res = await server.post('/api/users.list', { + body: { token: user.getJwtToken(), offset: 'blah' }, + }); + + expect(res.status).toEqual(400); + }); +});