From ace81310036c480e8dd80f5a5ba3e0fe44a9adf2 Mon Sep 17 00:00:00 2001 From: Gustavo Date: Wed, 19 Aug 2020 20:18:12 -0300 Subject: [PATCH] feat: validate command received in /commands --- package.json | 1 + src/apis/https/commands.test.ts | 18 +++++++++++++++ src/apis/https/commands.ts | 40 +++++++++++++++++++++++++++++++++ yarn.lock | 10 +++++++++ 4 files changed, 69 insertions(+) diff --git a/package.json b/package.json index b5fd139..90bad5b 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ "test": "./scripts/test.js" }, "dependencies": { + "ajv": "^6.12.4", "cors": "^2.8.5", "express": "^4.17.1" }, diff --git a/src/apis/https/commands.test.ts b/src/apis/https/commands.test.ts index 517dd05..4be7db9 100644 --- a/src/apis/https/commands.test.ts +++ b/src/apis/https/commands.test.ts @@ -56,6 +56,24 @@ describe('/commands endpoint', () => { }, ) + it('returns an error if the command is invalid', async () => { + const res = await fetch(endpoint, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + contextName: 'shopping', + aggregateName: 'cart', + // 'aggregateId' left out to make the command invalid + name: 'initialize', + data: null, + }), + }) + + expect(res.status).toBe(422) + }) + it('returns the ID of the new event', async () => { const res = await fetch(endpoint, { method: 'POST', diff --git a/src/apis/https/commands.ts b/src/apis/https/commands.ts index f04641a..1fafd33 100644 --- a/src/apis/https/commands.ts +++ b/src/apis/https/commands.ts @@ -1,3 +1,4 @@ +import Ajv from 'ajv' import cors from 'cors' import express from 'express' import firebaseAdmin from 'firebase-admin' @@ -10,6 +11,37 @@ import { createEventStore } from '../../stores/event-store' import { validateFirebaseIdToken } from './middlewares/validate-firebase-id-token' import { parseLocationFromHeaders } from './utils/parse-location-from-headers' +const commandSchema = { + type: 'object', + properties: { + contextName: { + type: 'string', + minLength: 1, + }, + aggregateName: { + type: 'string', + minLength: 1, + }, + aggregateId: { + type: 'string', + minLength: 1, + }, + name: { + type: 'string', + minLength: 1, + }, + data: { + type: ['object', 'null'], + properties: {}, + }, + }, + required: ['contextName', 'aggregateName', 'aggregateId', 'name', 'data'], + additionalProperties: false, +} + +const ajv = new Ajv() +const validateCommand = ajv.compile(commandSchema) + export const createCommandsEndpoint = ( firebaseAdminApp: firebaseAdmin.app.App, application: ApplicationDefinition, @@ -23,6 +55,14 @@ export const createCommandsEndpoint = ( app.post('/', async (req, res) => { try { + const isCommandValid = await validateCommand(req.body) + + if (!isCommandValid) { + console.error('Invalid command:', validateCommand.errors) + res.status(422).send('Invalid command') + return + } + const command: CommandWithMetadata = { contextName: req.body.contextName, aggregateName: req.body.aggregateName, diff --git a/yarn.lock b/yarn.lock index feaf861..ceb95f4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2164,6 +2164,16 @@ ajv@^6.10.0, ajv@^6.10.2, ajv@^6.5.5: json-schema-traverse "^0.4.1" uri-js "^4.2.2" +ajv@^6.12.4: + version "6.12.4" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.4.tgz#0614facc4522127fa713445c6bfd3ebd376e2234" + integrity sha512-eienB2c9qVQs2KWexhkrdMLVDoIQCz5KSeLxwg9Lzk4DOfBtIK9PQwwufcsn1jjGuf9WZmqPMbGxOzfcuphJCQ== + dependencies: + fast-deep-equal "^3.1.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + alphanum-sort@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/alphanum-sort/-/alphanum-sort-1.0.2.tgz#97a1119649b211ad33691d9f9f486a8ec9fbe0a3"