diff --git a/packages/server/src/api/controllers/webhook.js b/packages/server/src/api/controllers/webhook.js index 49ab652cbf..9958560e43 100644 --- a/packages/server/src/api/controllers/webhook.js +++ b/packages/server/src/api/controllers/webhook.js @@ -18,10 +18,6 @@ function Webhook(name, type, target) { exports.Webhook = Webhook -exports.WebhookType = { - AUTOMATION: "automation", -} - exports.fetch = async ctx => { const db = getAppDB() const response = await db.allDocs( diff --git a/packages/server/src/api/routes/automation.js b/packages/server/src/api/routes/automation.js index 2bec83d75e..be1af1f786 100644 --- a/packages/server/src/api/routes/automation.js +++ b/packages/server/src/api/routes/automation.js @@ -1,50 +1,20 @@ const Router = require("@koa/router") const controller = require("../controllers/automation") const authorized = require("../../middleware/authorized") -const joiValidator = require("../../middleware/joi-validator") const { BUILDER, PermissionLevels, PermissionTypes, } = require("@budibase/backend-core/permissions") -const Joi = require("joi") const { bodyResource, paramResource } = require("../../middleware/resourceId") const { middleware: appInfoMiddleware, AppType, } = require("../../middleware/appInfo") +const { automationValidator } = require("./utils/validators") const router = Router() -// prettier-ignore -function generateStepSchema(allowStepTypes) { - return Joi.object({ - stepId: Joi.string().required(), - id: Joi.string().required(), - description: Joi.string().required(), - name: Joi.string().required(), - tagline: Joi.string().required(), - icon: Joi.string().required(), - params: Joi.object(), - args: Joi.object(), - type: Joi.string().required().valid(...allowStepTypes), - }).unknown(true) -} - -function generateValidator(existing = false) { - // prettier-ignore - return joiValidator.body(Joi.object({ - _id: existing ? Joi.string().required() : Joi.string(), - _rev: existing ? Joi.string().required() : Joi.string(), - name: Joi.string().required(), - type: Joi.string().valid("automation").required(), - definition: Joi.object({ - steps: Joi.array().required().items(generateStepSchema(["ACTION", "LOGIC"])), - trigger: generateStepSchema(["TRIGGER"]).allow(null), - }).required().unknown(true), - }).unknown(true)) -} - router .get( "/api/automations/trigger/list", @@ -72,13 +42,13 @@ router "/api/automations", bodyResource("_id"), authorized(BUILDER), - generateValidator(true), + automationValidator(true), controller.update ) .post( "/api/automations", authorized(BUILDER), - generateValidator(false), + automationValidator(false), controller.create ) .delete( diff --git a/packages/server/src/api/routes/datasource.js b/packages/server/src/api/routes/datasource.js index 270e4e286e..21df11b55c 100644 --- a/packages/server/src/api/routes/datasource.js +++ b/packages/server/src/api/routes/datasource.js @@ -1,64 +1,18 @@ const Router = require("@koa/router") const datasourceController = require("../controllers/datasource") const authorized = require("../../middleware/authorized") -const joiValidator = require("../../middleware/joi-validator") const { BUILDER, PermissionLevels, PermissionTypes, } = require("@budibase/backend-core/permissions") -const Joi = require("joi") -const { DataSourceOperation } = require("../../constants") +const { + datasourceValidator, + datasourceQueryValidator, +} = require("./utils/validators") const router = Router() -function generateDatasourceSchema() { - // prettier-ignore - return joiValidator.body(Joi.object({ - _id: Joi.string(), - _rev: Joi.string(), - // source: Joi.string().valid("POSTGRES_PLUS"), - type: Joi.string().allow("datasource_plus"), - relationships: Joi.array().items(Joi.object({ - from: Joi.string().required(), - to: Joi.string().required(), - cardinality: Joi.valid("1:N", "1:1", "N:N").required() - })), - // entities: Joi.array().items(Joi.object({ - // type: Joi.string().valid(...Object.values(FieldTypes)).required(), - // name: Joi.string().required(), - // })), - }).unknown(true)) -} - -function generateQueryDatasourceSchema() { - // prettier-ignore - return joiValidator.body(Joi.object({ - endpoint: Joi.object({ - datasourceId: Joi.string().required(), - operation: Joi.string().required().valid(...Object.values(DataSourceOperation)), - entityId: Joi.string().required(), - }).required(), - resource: Joi.object({ - fields: Joi.array().items(Joi.string()).optional(), - }).optional(), - body: Joi.object().optional(), - sort: Joi.object().optional(), - filters: Joi.object({ - string: Joi.object().optional(), - range: Joi.object().optional(), - equal: Joi.object().optional(), - notEqual: Joi.object().optional(), - empty: Joi.object().optional(), - notEmpty: Joi.object().optional(), - }).optional(), - paginate: Joi.object({ - page: Joi.string().alphanum().optional(), - limit: Joi.number().optional(), - }).optional(), - })) -} - router .get("/api/datasources", authorized(BUILDER), datasourceController.fetch) .get( @@ -74,7 +28,7 @@ router .post( "/api/datasources/query", authorized(PermissionTypes.TABLE, PermissionLevels.READ), - generateQueryDatasourceSchema(), + datasourceQueryValidator(), datasourceController.query ) .post( @@ -85,7 +39,7 @@ router .post( "/api/datasources", authorized(BUILDER), - generateDatasourceSchema(), + datasourceValidator(), datasourceController.save ) .delete( diff --git a/packages/server/src/api/routes/permission.js b/packages/server/src/api/routes/permission.js index a868219e83..831b6dd004 100644 --- a/packages/server/src/api/routes/permission.js +++ b/packages/server/src/api/routes/permission.js @@ -1,25 +1,11 @@ const Router = require("@koa/router") const controller = require("../controllers/permission") const authorized = require("../../middleware/authorized") -const { - BUILDER, - PermissionLevels, -} = require("@budibase/backend-core/permissions") -const Joi = require("joi") -const joiValidator = require("../../middleware/joi-validator") +const { BUILDER } = require("@budibase/backend-core/permissions") +const { permissionValidator } = require("./utils/validators") const router = Router() -function generateValidator() { - const permLevelArray = Object.values(PermissionLevels) - // prettier-ignore - return joiValidator.params(Joi.object({ - level: Joi.string().valid(...permLevelArray).required(), - resourceId: Joi.string(), - roleId: Joi.string(), - }).unknown(true)) -} - router .get("/api/permission/builtin", authorized(BUILDER), controller.fetchBuiltin) .get("/api/permission/levels", authorized(BUILDER), controller.fetchLevels) @@ -33,14 +19,14 @@ router .post( "/api/permission/:roleId/:resourceId/:level", authorized(BUILDER), - generateValidator(), + permissionValidator(), controller.addPermission ) // deleting the level defaults it back the underlying access control for the resource .delete( "/api/permission/:roleId/:resourceId/:level", authorized(BUILDER), - generateValidator(), + permissionValidator(), controller.removePermission ) diff --git a/packages/server/src/api/routes/public/tables.js b/packages/server/src/api/routes/public/tables.js index bb875cad1b..fe2d9e82e2 100644 --- a/packages/server/src/api/routes/public/tables.js +++ b/packages/server/src/api/routes/public/tables.js @@ -1,5 +1,6 @@ const controller = require("../../controllers/public/tables") const Endpoint = require("./utils/Endpoint") +const { tableValidator } = require("../utils/validators") const read = [], write = [] @@ -66,7 +67,11 @@ read.push(new Endpoint("post", "/tables/search", controller.search)) * table: * $ref: '#/components/examples/table' */ -write.push(new Endpoint("post", "/tables", controller.create)) +write.push( + new Endpoint("post", "/tables", controller.create).addMiddleware( + tableValidator() + ) +) /** * @openapi @@ -97,7 +102,11 @@ write.push(new Endpoint("post", "/tables", controller.create)) * table: * $ref: '#/components/examples/table' */ -write.push(new Endpoint("put", "/tables/:tableId", controller.update)) +write.push( + new Endpoint("put", "/tables/:tableId", controller.update).addMiddleware( + tableValidator() + ) +) /** * @openapi diff --git a/packages/server/src/api/routes/role.js b/packages/server/src/api/routes/role.js index bf20b01221..107d9ec583 100644 --- a/packages/server/src/api/routes/role.js +++ b/packages/server/src/api/routes/role.js @@ -1,34 +1,13 @@ const Router = require("@koa/router") const controller = require("../controllers/role") const authorized = require("../../middleware/authorized") -const Joi = require("joi") -const joiValidator = require("../../middleware/joi-validator") -const { - BUILTIN_PERMISSION_IDS, - BUILDER, - PermissionLevels, -} = require("@budibase/backend-core/permissions") +const { BUILDER } = require("@budibase/backend-core/permissions") +const { roleValidator } = require("./utils/validators") const router = Router() -function generateValidator() { - const permLevelArray = Object.values(PermissionLevels) - // prettier-ignore - return joiValidator.body(Joi.object({ - _id: Joi.string().optional(), - _rev: Joi.string().optional(), - name: Joi.string().required(), - // this is the base permission ID (for now a built in) - permissionId: Joi.string().valid(...Object.values(BUILTIN_PERMISSION_IDS)).required(), - permissions: Joi.object() - .pattern(/.*/, [Joi.string().valid(...permLevelArray)]) - .optional(), - inherits: Joi.string().optional(), - }).unknown(true)) -} - router - .post("/api/roles", authorized(BUILDER), generateValidator(), controller.save) + .post("/api/roles", authorized(BUILDER), roleValidator(), controller.save) .get("/api/roles", authorized(BUILDER), controller.fetch) .get("/api/roles/:roleId", authorized(BUILDER), controller.find) .delete("/api/roles/:roleId/:rev", authorized(BUILDER), controller.destroy) diff --git a/packages/server/src/api/routes/screen.js b/packages/server/src/api/routes/screen.js index cfce67a5dc..e33a026126 100644 --- a/packages/server/src/api/routes/screen.js +++ b/packages/server/src/api/routes/screen.js @@ -2,40 +2,13 @@ const Router = require("@koa/router") const controller = require("../controllers/screen") const authorized = require("../../middleware/authorized") const { BUILDER } = require("@budibase/backend-core/permissions") -const joiValidator = require("../../middleware/joi-validator") -const Joi = require("joi") +const { screenValidator } = require("./utils/validators") const router = Router() -function generateSaveValidation() { - // prettier-ignore - return joiValidator.body(Joi.object({ - name: Joi.string().required(), - routing: Joi.object({ - route: Joi.string().required(), - roleId: Joi.string().required().allow(""), - }).required().unknown(true), - props: Joi.object({ - _id: Joi.string().required(), - _component: Joi.string().required(), - _children: Joi.array().required(), - _instanceName: Joi.string().required(), - _styles: Joi.object().required(), - type: Joi.string().optional(), - table: Joi.string().optional(), - layoutId: Joi.string().optional(), - }).required().unknown(true), - }).unknown(true)) -} - router .get("/api/screens", authorized(BUILDER), controller.fetch) - .post( - "/api/screens", - authorized(BUILDER), - generateSaveValidation(), - controller.save - ) + .post("/api/screens", authorized(BUILDER), screenValidator(), controller.save) .delete( "/api/screens/:screenId/:screenRev", authorized(BUILDER), diff --git a/packages/server/src/api/routes/table.js b/packages/server/src/api/routes/table.js index f3ca13313b..4d20b98962 100644 --- a/packages/server/src/api/routes/table.js +++ b/packages/server/src/api/routes/table.js @@ -7,25 +7,10 @@ const { PermissionLevels, PermissionTypes, } = require("@budibase/backend-core/permissions") -const joiValidator = require("../../middleware/joi-validator") -const Joi = require("joi") +const { tableValidator } = require("./utils/validators") const router = Router() -function generateSaveValidator() { - // prettier-ignore - return joiValidator.body(Joi.object({ - _id: Joi.string(), - _rev: Joi.string(), - type: Joi.string().valid("table", "internal", "external"), - primaryDisplay: Joi.string(), - schema: Joi.object().required(), - name: Joi.string().required(), - views: Joi.object(), - dataImport: Joi.object(), - }).unknown(true)) -} - router /** * @api {get} /api/tables Fetch all tables @@ -136,7 +121,7 @@ router // allows control over updating a table bodyResource("_id"), authorized(BUILDER), - generateSaveValidator(), + tableValidator(), tableController.save ) /** diff --git a/packages/server/src/api/routes/utils/validators.js b/packages/server/src/api/routes/utils/validators.js new file mode 100644 index 0000000000..ea56c86b6a --- /dev/null +++ b/packages/server/src/api/routes/utils/validators.js @@ -0,0 +1,168 @@ +const joiValidator = require("../../../middleware/joi-validator") +const { DataSourceOperation } = require("../../../constants") +const { WebhookType } = require("../../../constants") +const { + BUILTIN_PERMISSION_IDS, + PermissionLevels, +} = require("@budibase/backend-core/permissions") + +const Joi = require("joi") + +exports.tableValidator = () => { + // prettier-ignore + return joiValidator.body(Joi.object({ + _id: Joi.string(), + _rev: Joi.string(), + type: Joi.string().valid("table", "internal", "external"), + primaryDisplay: Joi.string(), + schema: Joi.object().required(), + name: Joi.string().required(), + views: Joi.object(), + dataImport: Joi.object(), + }).unknown(true)) +} + +exports.nameValidator = () => { + // prettier-ignore + return joiValidator.body(Joi.object({ + name: Joi.string(), + })) +} + +exports.datasourceValidator = () => { + // prettier-ignore + return joiValidator.body(Joi.object({ + _id: Joi.string(), + _rev: Joi.string(), + // source: Joi.string().valid("POSTGRES_PLUS"), + type: Joi.string().allow("datasource_plus"), + relationships: Joi.array().items(Joi.object({ + from: Joi.string().required(), + to: Joi.string().required(), + cardinality: Joi.valid("1:N", "1:1", "N:N").required() + })), + // entities: Joi.array().items(Joi.object({ + // type: Joi.string().valid(...Object.values(FieldTypes)).required(), + // name: Joi.string().required(), + // })), + }).unknown(true)) +} + +exports.datasourceQueryValidator = () => { + // prettier-ignore + return joiValidator.body(Joi.object({ + endpoint: Joi.object({ + datasourceId: Joi.string().required(), + operation: Joi.string().required().valid(...Object.values(DataSourceOperation)), + entityId: Joi.string().required(), + }).required(), + resource: Joi.object({ + fields: Joi.array().items(Joi.string()).optional(), + }).optional(), + body: Joi.object().optional(), + sort: Joi.object().optional(), + filters: Joi.object({ + string: Joi.object().optional(), + range: Joi.object().optional(), + equal: Joi.object().optional(), + notEqual: Joi.object().optional(), + empty: Joi.object().optional(), + notEmpty: Joi.object().optional(), + }).optional(), + paginate: Joi.object({ + page: Joi.string().alphanum().optional(), + limit: Joi.number().optional(), + }).optional(), + })) +} + +exports.webhookValidator = () => { + // prettier-ignore + return joiValidator.body(Joi.object({ + live: Joi.bool(), + _id: Joi.string().optional(), + _rev: Joi.string().optional(), + name: Joi.string().required(), + bodySchema: Joi.object().optional(), + action: Joi.object({ + type: Joi.string().required().valid(WebhookType.AUTOMATION), + target: Joi.string().required(), + }).required(), + }).unknown(true)) +} + +exports.roleValidator = () => { + const permLevelArray = Object.values(PermissionLevels) + // prettier-ignore + return joiValidator.body(Joi.object({ + _id: Joi.string().optional(), + _rev: Joi.string().optional(), + name: Joi.string().required(), + // this is the base permission ID (for now a built in) + permissionId: Joi.string().valid(...Object.values(BUILTIN_PERMISSION_IDS)).required(), + permissions: Joi.object() + .pattern(/.*/, [Joi.string().valid(...permLevelArray)]) + .optional(), + inherits: Joi.string().optional(), + }).unknown(true)) +} + +exports.permissionValidator = () => { + const permLevelArray = Object.values(PermissionLevels) + // prettier-ignore + return joiValidator.params(Joi.object({ + level: Joi.string().valid(...permLevelArray).required(), + resourceId: Joi.string(), + roleId: Joi.string(), + }).unknown(true)) +} + +exports.screenValidator = () => { + // prettier-ignore + return joiValidator.body(Joi.object({ + name: Joi.string().required(), + routing: Joi.object({ + route: Joi.string().required(), + roleId: Joi.string().required().allow(""), + }).required().unknown(true), + props: Joi.object({ + _id: Joi.string().required(), + _component: Joi.string().required(), + _children: Joi.array().required(), + _instanceName: Joi.string().required(), + _styles: Joi.object().required(), + type: Joi.string().optional(), + table: Joi.string().optional(), + layoutId: Joi.string().optional(), + }).required().unknown(true), + }).unknown(true)) +} + +function generateStepSchema(allowStepTypes) { + // prettier-ignore + return Joi.object({ + stepId: Joi.string().required(), + id: Joi.string().required(), + description: Joi.string().required(), + name: Joi.string().required(), + tagline: Joi.string().required(), + icon: Joi.string().required(), + params: Joi.object(), + args: Joi.object(), + type: Joi.string().required().valid(...allowStepTypes), + }).unknown(true) +} + +exports.automationValidator = (existing = false) => { + // prettier-ignore + return joiValidator.body(Joi.object({ + _id: existing ? Joi.string().required() : Joi.string(), + _rev: existing ? Joi.string().required() : Joi.string(), + name: Joi.string().required(), + type: Joi.string().valid("automation").required(), + definition: Joi.object({ + steps: Joi.array().required().items(generateStepSchema(["ACTION", "LOGIC"])), + trigger: generateStepSchema(["TRIGGER"]).allow(null), + }).required().unknown(true), + }).unknown(true)) +} diff --git a/packages/server/src/api/routes/webhook.js b/packages/server/src/api/routes/webhook.js index 561eb8c1ee..9635638700 100644 --- a/packages/server/src/api/routes/webhook.js +++ b/packages/server/src/api/routes/webhook.js @@ -1,33 +1,17 @@ const Router = require("@koa/router") const controller = require("../controllers/webhook") const authorized = require("../../middleware/authorized") -const joiValidator = require("../../middleware/joi-validator") const { BUILDER } = require("@budibase/backend-core/permissions") -const Joi = require("joi") +const { webhookValidator } = require("./utils/validators") const router = Router() -function generateSaveValidator() { - // prettier-ignore - return joiValidator.body(Joi.object({ - live: Joi.bool(), - _id: Joi.string().optional(), - _rev: Joi.string().optional(), - name: Joi.string().required(), - bodySchema: Joi.object().optional(), - action: Joi.object({ - type: Joi.string().required().valid(controller.WebhookType.AUTOMATION), - target: Joi.string().required(), - }).required(), - }).unknown(true)) -} - router .get("/api/webhooks", authorized(BUILDER), controller.fetch) .put( "/api/webhooks", authorized(BUILDER), - generateSaveValidator(), + webhookValidator(), controller.save ) .delete("/api/webhooks/:id/:rev", authorized(BUILDER), controller.destroy) diff --git a/packages/server/src/constants/index.js b/packages/server/src/constants/index.js index 16a98e5c58..cf89f1fe99 100644 --- a/packages/server/src/constants/index.js +++ b/packages/server/src/constants/index.js @@ -186,5 +186,9 @@ exports.BuildSchemaErrors = { INVALID_COLUMN: "invalid_column", } +exports.WebhookType = { + AUTOMATION: "automation", +} + // pass through the list from the auth/core lib exports.ObjectStoreBuckets = ObjectStoreBuckets