diff --git a/core/controllers/job/create.js b/core/controllers/job/create.js index 5de80865..cf0acd0d 100644 --- a/core/controllers/job/create.js +++ b/core/controllers/job/create.js @@ -43,16 +43,3 @@ module.exports = (server) => { createJob ) } - -//const createFromFiles = (req, res, next) => { -// -// const form = formidable({ multiples: true }) -// -// form.parse(req, (err, fields, files) => { -// if (err) { -// res.sendError(err) -// return; -// } -// res.send(200,{ fields, files }) -// }) -//} diff --git a/core/controllers/task/gateway.js b/core/controllers/task/gateway.js new file mode 100644 index 00000000..eebae5b7 --- /dev/null +++ b/core/controllers/task/gateway.js @@ -0,0 +1,51 @@ +const restify = require('restify') +const App = require('../../app') +const createJob = require('../../service/job/create') +const router = require('../../router') +const JobConstants = require('../../constants/jobs') +const { ClientError, ServerError } = require('../../lib/error-handler') + +const OpenAPIRequestValidator = require('openapi-request-validator').default + + +module.exports = (server) => { + const tasksMiddlewares = [ + server.auth.bearerMiddleware, + router.resolve.customerSessionToEntity(), + router.resolve.idToEntity({ param: 'task', required: true }), + router.ensureCustomer, + router.requireCredential('user'), + router.ensureAllowed({ entity: { name: 'task' } }), + (req, res, next) => { + try { + const task = req.task + + const spec = task.gateway?.openapi_spec + if (!spec || spec.operation !== req.method.toLowerCase()) { + throw new ClientError('Method Not Allowed', { statusCode: 405 }) + } + + const requestValidator = new OpenAPIRequestValidator(spec) + const validator = requestValidator.validateRequest(req) + + if (validator !== undefined) { + const err = new ClientError('Bad Request', { statusCode: validator.status }) + err.errors = validator.errors + throw err + } + + next() + } catch (err) { + res.sendError(err) + } + }, + createJob + ] + + //server.get('/task/:task/path/:pathuuid', tasksMiddlewares) + server.get('/task/:task/gateway', tasksMiddlewares) + server.post('/task/:task/gateway', tasksMiddlewares) + server.put('/task/:task/gateway', tasksMiddlewares) + server.del('/task/:task/gateway', tasksMiddlewares) + server.patch('/task/:task/gateway', tasksMiddlewares) +} diff --git a/core/controllers/task/index.js b/core/controllers/task/index.js index 8adc334e..9e6dd346 100644 --- a/core/controllers/task/index.js +++ b/core/controllers/task/index.js @@ -1,4 +1,5 @@ const crud = require('./crud') +const gateway = require('./gateway') const scheduler = require('./scheduler') const serialize = require('./serialize') const integrations = require('./integrations') @@ -7,6 +8,7 @@ const acl = require('./acl') module.exports = (server) => { crud(server) + gateway(server) scheduler(server) serialize(server) integrations(server) diff --git a/core/controllers/workflow/gateway.js b/core/controllers/workflow/gateway.js new file mode 100644 index 00000000..8ba6fb3e --- /dev/null +++ b/core/controllers/workflow/gateway.js @@ -0,0 +1,32 @@ +const restify = require('restify') +const App = require('../../app') +const createJob = require('../../service/job/create') +const router = require('../../router') +const JobConstants = require('../../constants/jobs') +const { ClientError, ServerError } = require('../../lib/error-handler') + +const OpenAPIRequestValidator = require('openapi-request-validator').default + +module.exports = (server) => { + // GATEWAY METHODS + const workflowMiddlewares = [ + server.auth.bearerMiddleware, + router.resolve.customerSessionToEntity(), + router.resolve.idToEntity({ param: 'workflow', required: true }), + router.ensureCustomer, + router.requireCredential('user'), + router.ensureAllowed({ entity: { name: 'workflow' } }), + (req, res, next) => { + const workflow = req.workflow + const method = req.method + next() + }, + createJob + ] + + server.get('/workflow/:workflow/gateway', workflowMiddlewares) + server.post('/workflow/:workflow/gateway', workflowMiddlewares) + server.put('/workflow/:workflow/gateway', workflowMiddlewares) + server.del('/workflow/:workflow/gateway', workflowMiddlewares) + server.patch('/workflow/:workflow/gateway', workflowMiddlewares) +} diff --git a/core/controllers/workflow/index.js b/core/controllers/workflow/index.js index 59c25208..d33442ee 100644 --- a/core/controllers/workflow/index.js +++ b/core/controllers/workflow/index.js @@ -1,5 +1,6 @@ const crud = require('./crud') const graph = require('./graph') +const gateway = require('./gateway') const job = require('./job') const acl = require('./acl') const triggers = require('./triggers') @@ -8,6 +9,7 @@ const scheduler = require('./scheduler') const serialization = require('./serialization') module.exports = (server) => { + gateway(server) job(server) serialization(server) acl(server) diff --git a/core/entity/base-schema.js b/core/entity/base-schema.js index 37f7efa0..1afd5c81 100644 --- a/core/entity/base-schema.js +++ b/core/entity/base-schema.js @@ -1,5 +1,3 @@ -'use strict' - const util = require('util') const mongoose = require('mongoose') const Schema = mongoose.Schema diff --git a/core/entity/task/base-properties.js b/core/entity/task/base-properties.js index 41257a78..a6e2dae2 100644 --- a/core/entity/task/base-properties.js +++ b/core/entity/task/base-properties.js @@ -1,5 +1,6 @@ -'use strict' -const ObjectId = require('mongoose').Types.ObjectId +const mongoose = require('mongoose') +const ObjectId = mongoose.Types.ObjectId +const Schema = mongoose.Schema const randomSecret = require('../../lib/random-secret') module.exports = { order: { type: Number, default: 0 }, @@ -53,4 +54,15 @@ module.exports = { version: { type: Number, 'default': 1 }, fingerprint: { type: String, 'default': '' }, //handle_errors: { type: Boolean } + gateway: { + // + // Docs https://swagger.io/docs/specification/basic-structure/ + // + openapi_spec: new Schema({ + openapi: { type: String }, // openapi spec version + path: { type: String }, // UUID auto generated to identify this path + operation: { type: String }, // method + parameters: [{ type: Object }] // task arguments using openapi spec + }) + } } diff --git a/package-lock.json b/package-lock.json index f4ab62d2..f50ff6e4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "theeye-supervisor", - "version": "3.1.0", + "version": "4.1.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -131,6 +131,25 @@ } } }, + "ajv": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", + "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", + "requires": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + } + }, + "ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "requires": { + "ajv": "^8.0.0" + } + }, "ansi-align": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", @@ -716,6 +735,11 @@ "xdg-basedir": "^4.0.0" } }, + "content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" + }, "core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", @@ -1054,6 +1078,11 @@ "resolved": "https://registry.npmjs.org/fast-decode-uri-component/-/fast-decode-uri-component-1.0.1.tgz", "integrity": "sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg==" }, + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" + }, "fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", @@ -1522,6 +1551,11 @@ "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" }, + "json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" + }, "json5": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/json5/-/json5-0.4.0.tgz", @@ -2087,6 +2121,32 @@ "wrappy": "1" } }, + "openapi-jsonschema-parameters": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/openapi-jsonschema-parameters/-/openapi-jsonschema-parameters-12.0.0.tgz", + "integrity": "sha512-OqPabvo3qEUpgG2blD+CqAUNC+/RNYsUKEqY3vSAu2syxrabRNxtaagLhA5WdieVQPLFuOlSX13TFUu8R/+kbA==", + "requires": { + "openapi-types": "^12.0.0" + } + }, + "openapi-request-validator": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/openapi-request-validator/-/openapi-request-validator-12.0.0.tgz", + "integrity": "sha512-z0K3fRViKbamg80RJgNLzLZik2QBvXVfHgtpK4nGlTQ7qgn06PApFzF1Arc4Fb3aAB26BFBQGzjrojT58XjA3A==", + "requires": { + "ajv": "^8.3.0", + "ajv-formats": "^2.1.0", + "content-type": "^1.0.4", + "openapi-jsonschema-parameters": "^12.0.0", + "openapi-types": "^12.0.0", + "ts-log": "^2.1.4" + } + }, + "openapi-types": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/openapi-types/-/openapi-types-12.0.0.tgz", + "integrity": "sha512-6Wd9k8nmGQHgCbehZCP6wwWcfXcvinhybUTBatuhjRsCxUIujuYFZc9QnGeae75CyHASewBtxs0HX/qwREReUw==" + }, "optional-require": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/optional-require/-/optional-require-1.1.0.tgz", @@ -2452,6 +2512,11 @@ "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=" }, + "require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==" + }, "require-main-filename": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", @@ -2866,6 +2931,11 @@ "nopt": "~1.0.10" } }, + "ts-log": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/ts-log/-/ts-log-2.2.4.tgz", + "integrity": "sha512-DEQrfv6l7IvN2jlzc/VTdZJYsWUnQNCsueYjMkC/iXoEoi5fNan6MjeDqkvhfzbmHgdz9UxDUluX3V5HdjTydQ==" + }, "tweetnacl": { "version": "0.14.5", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", @@ -2995,6 +3065,21 @@ } } }, + "uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "requires": { + "punycode": "^2.1.0" + }, + "dependencies": { + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" + } + } + }, "url": { "version": "0.10.3", "resolved": "https://registry.npmjs.org/url/-/url-0.10.3.tgz", diff --git a/package.json b/package.json index 50d5aeef..21752aa3 100644 --- a/package.json +++ b/package.json @@ -47,6 +47,7 @@ "multer": "~1.4.2", "nodemailer": "~6.7.0", "nodemon": "~2.0.4", + "openapi-request-validator": "~12.0.0", "passport": "~0.3.2", "passport-http": "~0.3.0", "passport-http-bearer": "~1.0.1",