diff --git a/src/Item/Item.router.spec.ts b/src/Item/Item.router.spec.ts index a062f3b4..eac5ae15 100644 --- a/src/Item/Item.router.spec.ts +++ b/src/Item/Item.router.spec.ts @@ -429,27 +429,6 @@ describe('Item router (old)', () => { }) }) - describe('when payload schema is invalid', () => { - it('should fail with invalid schema', async () => { - await testError( - { - query: { - id: 'id', - }, - body: { - item: { - id: 'id', - }, - }, - auth: { - ethAddress: testItem.eth_address, - }, - }, - 'Invalid schema' - ) - }) - }) - describe('when is_approved is sent in the payload', () => { it('should fail with cant set is_approved message', async () => { await testError( diff --git a/src/Item/Item.router.ts b/src/Item/Item.router.ts index 1ad7686f..ef0b4b09 100644 --- a/src/Item/Item.router.ts +++ b/src/Item/Item.router.ts @@ -7,13 +7,13 @@ import { HTTPError, STATUS_CODES } from '../common/HTTPError' import { collectionAPI } from '../ethereum/api/collection' import { Bridge } from '../ethereum/api/Bridge' import { peerAPI } from '../ethereum/api/peer' -import { getValidator } from '../utils/validator' import { withModelAuthorization, withAuthentication, withModelExists, AuthRequest, withLowercasedParams, + withSchemaValidation, } from '../middleware' import { Ownable } from '../Ownable' import { S3Item, getFileUploader, ACL, S3Content } from '../S3' @@ -27,13 +27,11 @@ import { hasAccess as hasCollectionAccess } from '../Collection/access' import { isCommitteeMember } from '../Committee' import { Item } from './Item.model' import { ItemAttributes } from './Item.types' -import { itemSchema } from './Item.schema' +import { upsertItemSchema } from './Item.schema' import { FullItem } from './Item.types' import { hasAccess } from './access' import { getDecentralandItemURN, toDBItem } from './utils' -const validator = getValidator() - export class ItemRouter extends Router { itemFilesRequestHandler: | ((req: Request, res: Response) => Promise) // Promisified RequestHandler @@ -93,6 +91,7 @@ export class ItemRouter extends Router { this.router.put( '/items/:id', withAuthentication, + withSchemaValidation(upsertItemSchema), server.handleRequest(this.upsertItem) ) @@ -315,14 +314,6 @@ export class ItemRouter extends Router { ) } - const validate = validator.compile(itemSchema) - - validate(itemJSON) - - if (validate.errors) { - throw new HTTPError('Invalid schema', validate.errors) - } - const canUpsert = await new Ownable(Item).canUpsert(id, eth_address) if (!canUpsert) { diff --git a/src/Item/Item.schema.ts b/src/Item/Item.schema.ts index 16d2f8e6..bedd7d74 100644 --- a/src/Item/Item.schema.ts +++ b/src/Item/Item.schema.ts @@ -48,3 +48,12 @@ export const itemSchema = Object.freeze({ 'updated_at', ], }) + +export const upsertItemSchema = Object.freeze({ + type: 'object', + properties: { + item: itemSchema, + }, + additionalProperties: false, + required: ['item'], +}) diff --git a/src/middleware/index.ts b/src/middleware/index.ts index a9ef2af6..deef8a0d 100644 --- a/src/middleware/index.ts +++ b/src/middleware/index.ts @@ -2,6 +2,7 @@ export * from './asMiddleware' export * from './authentication-legacy' export * from './authentication' export * from './authorization' +export * from './schemaValidator' export * from './logger' export * from './model' export * from './url' diff --git a/src/middleware/schemaValidator.spec.ts b/src/middleware/schemaValidator.spec.ts new file mode 100644 index 00000000..c39cb05d --- /dev/null +++ b/src/middleware/schemaValidator.spec.ts @@ -0,0 +1,90 @@ +import supertest from 'supertest' +import express from 'express' +import { Schema } from 'ajv' +import { buildURL } from '../../spec/utils' +import { app } from '../server' +import { withSchemaValidation } from './schemaValidator' + +const schema: Schema = Object.freeze({ + type: 'object', + properties: { + id: { type: 'string' }, + }, + additionalProperties: false, + required: ['id'], +}) + +const simpleResponseHandler = (_: express.Request, res: express.Response) => { + res.status(200).end() +} + +app + .getRouter() + .post('/test', withSchemaValidation(schema), simpleResponseHandler) +const server = supertest(app.getApp()) + +let data: any +describe('when posting invalid data', () => { + beforeEach(() => { + data = { id: 234 } + }) + + it('should respond with a 400 and a detailed description of the validation errors', () => { + return server + .post(buildURL('/test')) + .send(data) + .expect(400) + .then((response) => { + expect(response.body).toEqual({ + error: 'Invalid request body', + data: [ + { + dataPath: '/id', + keyword: 'type', + message: 'should be string', + params: { type: 'string' }, + schemaPath: '#/properties/id/type', + }, + ], + ok: false, + }) + }) + }) +}) + +describe('when posting no data', () => { + it('should respond with a 400 and a detailed description of the validation errors', () => { + return server + .post(buildURL('/test')) + .expect(400) + .then((response) => { + expect(response.body).toEqual({ + error: 'Invalid request body', + data: [ + { + dataPath: '', + keyword: 'required', + message: "should have required property 'id'", + params: { missingProperty: 'id' }, + schemaPath: '#/required', + }, + ], + ok: false, + }) + }) + }) +}) + +describe('when posting the correct data', () => { + beforeEach(() => { + data = { id: 'anId' } + }) + + it("should respond with the handler's response", () => { + return server + .post(buildURL('/test')) + .send(data) + .expect(200) + .then(() => undefined) + }) +}) diff --git a/src/middleware/schemaValidator.ts b/src/middleware/schemaValidator.ts new file mode 100644 index 00000000..4d9aba5d --- /dev/null +++ b/src/middleware/schemaValidator.ts @@ -0,0 +1,26 @@ +import { Schema } from 'ajv' +import express from 'express' +import { HTTPError, STATUS_CODES } from '../common/HTTPError' +import { getValidator } from '../utils/validator' + +const validator = getValidator() + +export const withSchemaValidation = (schema: Schema) => ( + req: express.Request, + _: express.Response, + next: express.NextFunction +) => { + const validate = validator.compile(schema) + const valid = validate(req.body) + + if (!valid) { + next( + new HTTPError( + 'Invalid request body', + validate.errors, + STATUS_CODES.badRequest + ) + ) + } + next() +}