From 5b26fce1eaaba381b867395012ad65531f639fe2 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Tue, 10 Nov 2020 17:22:41 +0000 Subject: [PATCH 01/15] Starting work off towards routing of screens in backend, getting view ready and a bit of cleanup to make internal views easier to create. --- .../server/src/api/controllers/view/index.js | 5 +++-- .../server/src/db/linkedRows/linkUtils.js | 5 +++-- packages/server/src/db/utils.js | 10 +++++++++ packages/server/src/routing/index.js | 19 ++++++++++++++++ packages/server/src/routing/routingUtils.js | 22 +++++++++++++++++++ 5 files changed, 57 insertions(+), 4 deletions(-) create mode 100644 packages/server/src/routing/index.js create mode 100644 packages/server/src/routing/routingUtils.js diff --git a/packages/server/src/api/controllers/view/index.js b/packages/server/src/api/controllers/view/index.js index 57d4862b7e..0b5b18a93c 100644 --- a/packages/server/src/api/controllers/view/index.js +++ b/packages/server/src/api/controllers/view/index.js @@ -5,6 +5,7 @@ const { join } = require("../../../utilities/centralPath") const os = require("os") const exporters = require("./exporters") const { fetchView } = require("../row") +const { ViewNames } = require("../../../db/utils") const controller = { fetch: async ctx => { @@ -13,8 +14,8 @@ const controller = { const response = [] for (let name of Object.keys(designDoc.views)) { - // Only return custom views - if (name === "by_link") { + // Only return custom views, not built ins + if (Object.values(ViewNames).indexOf(name) !== -1) { continue } response.push({ diff --git a/packages/server/src/db/linkedRows/linkUtils.js b/packages/server/src/db/linkedRows/linkUtils.js index dc9d8d3f1e..3a9aff6c33 100644 --- a/packages/server/src/db/linkedRows/linkUtils.js +++ b/packages/server/src/db/linkedRows/linkUtils.js @@ -1,5 +1,6 @@ const CouchDB = require("../index") const Sentry = require("@sentry/node") +const { ViewNames, getQueryIndex } = require("../utils") /** * Only needed so that boolean parameters are being used for includeDocs @@ -40,7 +41,7 @@ exports.createLinkView = async appId => { } designDoc.views = { ...designDoc.views, - by_link: view, + [ViewNames.LINK]: view, } await db.put(designDoc) } @@ -76,7 +77,7 @@ exports.getLinkDocuments = async function({ } params.include_docs = !!includeDocs try { - const response = await db.query("database/by_link", params) + const response = await db.query(getQueryIndex(ViewNames.LINK), params) if (includeDocs) { return response.rows.map(row => row.doc) } else { diff --git a/packages/server/src/db/utils.js b/packages/server/src/db/utils.js index a213dc9066..4edb27a416 100644 --- a/packages/server/src/db/utils.js +++ b/packages/server/src/db/utils.js @@ -17,10 +17,20 @@ const DocumentTypes = { SCREEN: "screen", } +const ViewNames = { + LINK: "by_link", + ROUTING: "screen_routes", +} + +exports.ViewNames = ViewNames exports.DocumentTypes = DocumentTypes exports.SEPARATOR = SEPARATOR exports.UNICODE_MAX = UNICODE_MAX +exports.getQueryIndex = viewName => { + return `database/${viewName}` +} + /** * If creating DB allDocs/query params with only a single top level ID this can be used, this * is usually the case as most of our docs are top level e.g. tables, automations, users and so on. diff --git a/packages/server/src/routing/index.js b/packages/server/src/routing/index.js new file mode 100644 index 0000000000..5de85861b4 --- /dev/null +++ b/packages/server/src/routing/index.js @@ -0,0 +1,19 @@ +const CouchDB = require("../db") +const { createRoutingView } = require("./routingUtils") +const { ViewNames, getQueryIndex } = require("../db/utils") + +exports.getRoutingInfo = async appId => { + const db = new CouchDB(appId) + try { + const allRouting = await db.query(getQueryIndex(ViewNames.ROUTING)) + return allRouting.rows.map(row => row.value) + } catch (err) { + // check if the view doesn't exist, it should for all new instances + if (err != null && err.name === "not_found") { + await createRoutingView(appId) + return exports.getRoutingInfo(appId) + } else { + throw err + } + } +} diff --git a/packages/server/src/routing/routingUtils.js b/packages/server/src/routing/routingUtils.js new file mode 100644 index 0000000000..7a51079b8a --- /dev/null +++ b/packages/server/src/routing/routingUtils.js @@ -0,0 +1,22 @@ +const CouchDB = require("../db") +const { DocumentTypes, SEPARATOR, ViewNames } = require("../db/utils") +const SCREEN_PREFIX = DocumentTypes.SCREEN + SEPARATOR + +exports.createRoutingView = async appId => { + const db = new CouchDB(appId) + const designDoc = await db.get("_design/database") + const view = { + map: function(doc) { + if (doc._id.startsWith(SCREEN_PREFIX)) { + emit(doc._id, { + routing: doc.routing, + }) + } + }.toString(), + } + designDoc.views = { + ...designDoc.views, + [ViewNames.ROUTING]: view, + } + await db.put(designDoc) +} From 1f4e27eb13d0c3d1fd9fad63bc2e095197aa1ec3 Mon Sep 17 00:00:00 2001 From: Michael Drury Date: Wed, 11 Nov 2020 17:34:15 +0000 Subject: [PATCH 02/15] WIP - this is working towards the permissions system but stopping here for the night, this is currently not functional. --- .../server/src/api/controllers/accesslevel.js | 11 +- packages/server/src/api/controllers/user.js | 10 +- packages/server/src/api/routes/accesslevel.js | 18 ++-- packages/server/src/api/routes/analytics.js | 2 +- packages/server/src/api/routes/apikeys.js | 2 +- packages/server/src/api/routes/application.js | 2 +- packages/server/src/api/routes/automation.js | 8 +- packages/server/src/api/routes/component.js | 2 +- packages/server/src/api/routes/deploy.js | 2 +- packages/server/src/api/routes/pages.js | 2 +- packages/server/src/api/routes/row.js | 19 ++-- packages/server/src/api/routes/screen.js | 2 +- packages/server/src/api/routes/static.js | 2 +- packages/server/src/api/routes/table.js | 8 +- packages/server/src/api/routes/templates.js | 2 +- .../src/api/routes/tests/accesslevel.spec.js | 16 +-- .../src/api/routes/tests/couchTestUtils.js | 2 +- .../server/src/api/routes/tests/user.spec.js | 2 +- packages/server/src/api/routes/user.js | 32 ++++-- packages/server/src/api/routes/view.js | 8 +- packages/server/src/api/routes/webhook.js | 8 +- .../src/automations/steps/createUser.js | 4 +- .../server/src/middleware/authenticated.js | 14 +-- packages/server/src/middleware/authorized.js | 50 +++------ packages/server/src/utilities/accessLevels.js | 36 ------- .../src/utilities/builder/setBuilderToken.js | 2 +- packages/server/src/utilities/permissions.js | 2 +- .../src/utilities/security/accessLevels.js | 44 ++++++++ .../src/utilities/security/permissions.js | 101 ++++++++++++++++++ 29 files changed, 263 insertions(+), 150 deletions(-) delete mode 100644 packages/server/src/utilities/accessLevels.js create mode 100644 packages/server/src/utilities/security/accessLevels.js create mode 100644 packages/server/src/utilities/security/permissions.js diff --git a/packages/server/src/api/controllers/accesslevel.js b/packages/server/src/api/controllers/accesslevel.js index 143c633e42..8c525ba52a 100644 --- a/packages/server/src/api/controllers/accesslevel.js +++ b/packages/server/src/api/controllers/accesslevel.js @@ -2,9 +2,8 @@ const CouchDB = require("../../db") const { generateAdminPermissions, generatePowerUserPermissions, - POWERUSER_LEVEL_ID, - ADMIN_LEVEL_ID, -} = require("../../utilities/accessLevels") + BUILTIN_LEVELS, +} = require("../../utilities/security/accessLevels") const { generateAccessLevelID, getAccessLevelParams, @@ -21,13 +20,11 @@ exports.fetch = async function(ctx) { const staticAccessLevels = [ { - _id: ADMIN_LEVEL_ID, - name: "Admin", + ...BUILTIN_LEVELS.admin, permissions: await generateAdminPermissions(ctx.user.appId), }, { - _id: POWERUSER_LEVEL_ID, - name: "Power User", + ...BUILTIN_LEVELS.power, permissions: await generatePowerUserPermissions(ctx.user.appId), }, ] diff --git a/packages/server/src/api/controllers/user.js b/packages/server/src/api/controllers/user.js index 5e4f963f5b..7d460c1db6 100644 --- a/packages/server/src/api/controllers/user.js +++ b/packages/server/src/api/controllers/user.js @@ -2,9 +2,8 @@ const CouchDB = require("../../db") const bcrypt = require("../../utilities/bcrypt") const { generateUserID, getUserParams } = require("../../db/utils") const { - POWERUSER_LEVEL_ID, - ADMIN_LEVEL_ID, -} = require("../../utilities/accessLevels") + BUILTIN_LEVELS_IDS, +} = require("../../utilities/security/accessLevels") exports.fetch = async function(ctx) { const database = new CouchDB(ctx.user.appId) @@ -89,10 +88,7 @@ exports.find = async function(ctx) { const checkAccessLevel = async (db, accessLevelId) => { if (!accessLevelId) return - if ( - accessLevelId === POWERUSER_LEVEL_ID || - accessLevelId === ADMIN_LEVEL_ID - ) { + if (BUILTIN_LEVELS_IDS.indexOf(accessLevelId) !== -1) { return { _id: accessLevelId, name: accessLevelId, diff --git a/packages/server/src/api/routes/accesslevel.js b/packages/server/src/api/routes/accesslevel.js index af1ff80ec6..f8bb70da63 100644 --- a/packages/server/src/api/routes/accesslevel.js +++ b/packages/server/src/api/routes/accesslevel.js @@ -1,14 +1,20 @@ const Router = require("@koa/router") const controller = require("../controllers/accesslevel") +const authorized = require("../../middleware/authorized") +const { BUILDER } = require("../../utilities/security/permissions") const router = Router() router - .post("/api/accesslevels", controller.create) - .put("/api/accesslevels", controller.update) - .get("/api/accesslevels", controller.fetch) - .get("/api/accesslevels/:levelId", controller.find) - .delete("/api/accesslevels/:levelId/:rev", controller.destroy) - .patch("/api/accesslevels/:levelId", controller.patch) + .post("/api/accesslevels", authorized(BUILDER), controller.create) + .put("/api/accesslevels", authorized(BUILDER), controller.update) + .get("/api/accesslevels", authorized(BUILDER), controller.fetch) + .get("/api/accesslevels/:levelId", authorized(BUILDER), controller.find) + .delete( + "/api/accesslevels/:levelId/:rev", + authorized(BUILDER), + controller.destroy + ) + .patch("/api/accesslevels/:levelId", authorized(BUILDER), controller.patch) module.exports = router diff --git a/packages/server/src/api/routes/analytics.js b/packages/server/src/api/routes/analytics.js index 626e3c2994..0d5e38c34d 100644 --- a/packages/server/src/api/routes/analytics.js +++ b/packages/server/src/api/routes/analytics.js @@ -1,7 +1,7 @@ const Router = require("@koa/router") const authorized = require("../../middleware/authorized") -const { BUILDER } = require("../../utilities/accessLevels") const controller = require("../controllers/analytics") +const { BUILDER } = require("../../utilities/security/permissions") const router = Router() diff --git a/packages/server/src/api/routes/apikeys.js b/packages/server/src/api/routes/apikeys.js index bec9ab677c..d6d0edeac0 100644 --- a/packages/server/src/api/routes/apikeys.js +++ b/packages/server/src/api/routes/apikeys.js @@ -1,7 +1,7 @@ const Router = require("@koa/router") const controller = require("../controllers/apikeys") const authorized = require("../../middleware/authorized") -const { BUILDER } = require("../../utilities/accessLevels") +const { BUILDER } = require("../../utilities/security/permissions") const router = Router() diff --git a/packages/server/src/api/routes/application.js b/packages/server/src/api/routes/application.js index aeb815d38c..e3b4ddf6cf 100644 --- a/packages/server/src/api/routes/application.js +++ b/packages/server/src/api/routes/application.js @@ -1,7 +1,7 @@ const Router = require("@koa/router") const controller = require("../controllers/application") const authorized = require("../../middleware/authorized") -const { BUILDER } = require("../../utilities/accessLevels") +const { BUILDER } = require("../../utilities/security/permissions") const router = Router() diff --git a/packages/server/src/api/routes/automation.js b/packages/server/src/api/routes/automation.js index 3ac7937da2..8644c75787 100644 --- a/packages/server/src/api/routes/automation.js +++ b/packages/server/src/api/routes/automation.js @@ -2,7 +2,11 @@ const Router = require("@koa/router") const controller = require("../controllers/automation") const authorized = require("../../middleware/authorized") const joiValidator = require("../../middleware/joi-validator") -const { BUILDER, EXECUTE_AUTOMATION } = require("../../utilities/accessLevels") +const { + BUILDER, + PermissionLevels, + PermissionTypes, +} = require("../../utilities/security/permissions") const Joi = require("joi") const router = Router() @@ -75,7 +79,7 @@ router ) .post( "/api/automations/:id/trigger", - authorized(EXECUTE_AUTOMATION), + authorized(PermissionTypes.AUTOMATION, PermissionLevels.EXECUTE), controller.trigger ) .delete("/api/automations/:id/:rev", authorized(BUILDER), controller.destroy) diff --git a/packages/server/src/api/routes/component.js b/packages/server/src/api/routes/component.js index 8fbe7ac41a..e9db3bee76 100644 --- a/packages/server/src/api/routes/component.js +++ b/packages/server/src/api/routes/component.js @@ -1,7 +1,7 @@ const Router = require("@koa/router") const controller = require("../controllers/component") const authorized = require("../../middleware/authorized") -const { BUILDER } = require("../../utilities/accessLevels") +const { BUILDER } = require("../../utilities/security/permissions") const router = Router() diff --git a/packages/server/src/api/routes/deploy.js b/packages/server/src/api/routes/deploy.js index 4f7aa9b33b..d8667c6fc1 100644 --- a/packages/server/src/api/routes/deploy.js +++ b/packages/server/src/api/routes/deploy.js @@ -1,7 +1,7 @@ const Router = require("@koa/router") const controller = require("../controllers/deploy") const authorized = require("../../middleware/authorized") -const { BUILDER } = require("../../utilities/accessLevels") +const { BUILDER } = require("../../utilities/security/permissions") const router = Router() diff --git a/packages/server/src/api/routes/pages.js b/packages/server/src/api/routes/pages.js index 1ec01dc780..43fb0e764c 100644 --- a/packages/server/src/api/routes/pages.js +++ b/packages/server/src/api/routes/pages.js @@ -1,6 +1,6 @@ const Router = require("@koa/router") const authorized = require("../../middleware/authorized") -const { BUILDER } = require("../../utilities/accessLevels") +const { BUILDER } = require("../../utilities/security/permissions") const controller = require("../controllers/page") const router = Router() diff --git a/packages/server/src/api/routes/row.js b/packages/server/src/api/routes/row.js index ae5ae772a6..4409f7f27b 100644 --- a/packages/server/src/api/routes/row.js +++ b/packages/server/src/api/routes/row.js @@ -2,46 +2,49 @@ const Router = require("@koa/router") const rowController = require("../controllers/row") const authorized = require("../../middleware/authorized") const usage = require("../../middleware/usageQuota") -const { READ_TABLE, WRITE_TABLE } = require("../../utilities/accessLevels") +const { + PermissionLevels, + PermissionTypes, +} = require("../../utilities/security/permissions") const router = Router() router .get( "/api/:tableId/:rowId/enrich", - authorized(READ_TABLE, ctx => ctx.params.tableId), + authorized(PermissionTypes.TABLE, PermissionLevels.READ), rowController.fetchEnrichedRow ) .get( "/api/:tableId/rows", - authorized(READ_TABLE, ctx => ctx.params.tableId), + authorized(PermissionTypes.TABLE, PermissionLevels.READ), rowController.fetchTableRows ) .get( "/api/:tableId/rows/:rowId", - authorized(READ_TABLE, ctx => ctx.params.tableId), + authorized(PermissionTypes.TABLE, PermissionLevels.READ), rowController.find ) .post("/api/rows/search", rowController.search) .post( "/api/:tableId/rows", - authorized(WRITE_TABLE, ctx => ctx.params.tableId), + authorized(PermissionTypes.TABLE, PermissionLevels.WRITE), usage, rowController.save ) .patch( "/api/:tableId/rows/:id", - authorized(WRITE_TABLE, ctx => ctx.params.tableId), + authorized(PermissionTypes.TABLE, PermissionLevels.WRITE), rowController.patch ) .post( "/api/:tableId/rows/validate", - authorized(WRITE_TABLE, ctx => ctx.params.tableId), + authorized(PermissionTypes.TABLE, PermissionLevels.WRITE), rowController.validate ) .delete( "/api/:tableId/rows/:rowId/:revId", - authorized(WRITE_TABLE, ctx => ctx.params.tableId), + authorized(PermissionTypes.TABLE, PermissionLevels.WRITE), usage, rowController.destroy ) diff --git a/packages/server/src/api/routes/screen.js b/packages/server/src/api/routes/screen.js index 407bbd1a94..1804bec27e 100644 --- a/packages/server/src/api/routes/screen.js +++ b/packages/server/src/api/routes/screen.js @@ -1,7 +1,7 @@ const Router = require("@koa/router") const controller = require("../controllers/screen") const authorized = require("../../middleware/authorized") -const { BUILDER } = require("../../utilities/accessLevels") +const { BUILDER } = require("../../utilities/security/permissions") const joiValidator = require("../../middleware/joi-validator") const Joi = require("joi") diff --git a/packages/server/src/api/routes/static.js b/packages/server/src/api/routes/static.js index 5c33900eca..a519a63781 100644 --- a/packages/server/src/api/routes/static.js +++ b/packages/server/src/api/routes/static.js @@ -3,7 +3,7 @@ const controller = require("../controllers/static") const { budibaseTempDir } = require("../../utilities/budibaseDir") const env = require("../../environment") const authorized = require("../../middleware/authorized") -const { BUILDER } = require("../../utilities/accessLevels") +const { BUILDER } = require("../../utilities/security/permissions") const usage = require("../../middleware/usageQuota") const router = Router() diff --git a/packages/server/src/api/routes/table.js b/packages/server/src/api/routes/table.js index 40bfa9326f..ef0eb7caec 100644 --- a/packages/server/src/api/routes/table.js +++ b/packages/server/src/api/routes/table.js @@ -1,7 +1,11 @@ const Router = require("@koa/router") const tableController = require("../controllers/table") const authorized = require("../../middleware/authorized") -const { BUILDER, READ_TABLE } = require("../../utilities/accessLevels") +const { + BUILDER, + PermissionLevels, + PermissionTypes, +} = require("../../utilities/security/permissions") const router = Router() @@ -9,7 +13,7 @@ router .get("/api/tables", authorized(BUILDER), tableController.fetch) .get( "/api/tables/:id", - authorized(READ_TABLE, ctx => ctx.params.id), + authorized(PermissionTypes.TABLE, PermissionLevels.READ), tableController.find ) .post("/api/tables", authorized(BUILDER), tableController.save) diff --git a/packages/server/src/api/routes/templates.js b/packages/server/src/api/routes/templates.js index 3e481610ce..05882a22ea 100644 --- a/packages/server/src/api/routes/templates.js +++ b/packages/server/src/api/routes/templates.js @@ -1,7 +1,7 @@ const Router = require("@koa/router") const controller = require("../controllers/templates") const authorized = require("../../middleware/authorized") -const { BUILDER } = require("../../utilities/accessLevels") +const { BUILDER } = require("../../utilities/security/permissions") const router = Router() diff --git a/packages/server/src/api/routes/tests/accesslevel.spec.js b/packages/server/src/api/routes/tests/accesslevel.spec.js index 3362cbd713..2fc4b54e20 100644 --- a/packages/server/src/api/routes/tests/accesslevel.spec.js +++ b/packages/server/src/api/routes/tests/accesslevel.spec.js @@ -8,11 +8,11 @@ const { const { generateAdminPermissions, generatePowerUserPermissions, - POWERUSER_LEVEL_ID, - ADMIN_LEVEL_ID, + BUILTIN_LEVELS, READ_TABLE, WRITE_TABLE, -} = require("../../../utilities/accessLevels") +} = require("../../../utilities/security/accessLevels") +const { BUILTIN_PERMISSION_NAMES } = require("../../../utilities/security/permissions") describe("/accesslevels", () => { let server @@ -59,7 +59,7 @@ describe("/accesslevels", () => { it("should list custom levels, plus 2 default levels", async () => { const createRes = await request .post(`/api/accesslevels`) - .send({ name: "user", permissions: [ { itemId: table._id, name: READ_TABLE }] }) + .send({ name: "user", permissions: [BUILTIN_PERMISSION_NAMES.READ_ONLY] }) .set(defaultHeaders(appId)) .expect('Content-Type', /json/) .expect(200) @@ -74,11 +74,11 @@ describe("/accesslevels", () => { expect(res.body.length).toBe(3) - const adminLevel = res.body.find(r => r._id === ADMIN_LEVEL_ID) + const adminLevel = res.body.find(r => r._id === BUILTIN_LEVELS.admin._id) expect(adminLevel).toBeDefined() expect(adminLevel.permissions).toEqual(await generateAdminPermissions(appId)) - const powerUserLevel = res.body.find(r => r._id === POWERUSER_LEVEL_ID) + const powerUserLevel = res.body.find(r => r._id === BUILTIN_LEVELS.power._id) expect(powerUserLevel).toBeDefined() expect(powerUserLevel.permissions).toEqual(await generatePowerUserPermissions(appId)) @@ -92,7 +92,7 @@ describe("/accesslevels", () => { it("should delete custom access level", async () => { const createRes = await request .post(`/api/accesslevels`) - .send({ name: "user", permissions: [ { itemId: table._id, name: READ_TABLE } ] }) + .send({ name: "user", permissions: [BUILTIN_PERMISSION_NAMES.READ_ONLY] }) .set(defaultHeaders(appId)) .expect('Content-Type', /json/) .expect(200) @@ -115,7 +115,7 @@ describe("/accesslevels", () => { it("should add given permissions", async () => { const createRes = await request .post(`/api/accesslevels`) - .send({ name: "user", permissions: [ { itemId: table._id, name: READ_TABLE }] }) + .send({ name: "user", permissions: [BUILTIN_PERMISSION_NAMES.READ_ONLY] }) .set(defaultHeaders(appId)) .expect('Content-Type', /json/) .expect(200) diff --git a/packages/server/src/api/routes/tests/couchTestUtils.js b/packages/server/src/api/routes/tests/couchTestUtils.js index 02a75d77fd..bbb2da903e 100644 --- a/packages/server/src/api/routes/tests/couchTestUtils.js +++ b/packages/server/src/api/routes/tests/couchTestUtils.js @@ -5,7 +5,7 @@ const { ANON_LEVEL_ID, BUILDER_LEVEL_ID, generateAdminPermissions, -} = require("../../../utilities/accessLevels") +} = require("../../../utilities/security/accessLevels") const packageJson = require("../../../../package") const jwt = require("jsonwebtoken") const env = require("../../../environment") diff --git a/packages/server/src/api/routes/tests/user.spec.js b/packages/server/src/api/routes/tests/user.spec.js index a569902bae..d0c12f2ea1 100644 --- a/packages/server/src/api/routes/tests/user.spec.js +++ b/packages/server/src/api/routes/tests/user.spec.js @@ -9,7 +9,7 @@ const { POWERUSER_LEVEL_ID, LIST_USERS, USER_MANAGEMENT -} = require("../../../utilities/accessLevels") +} = require("../../../utilities/security/accessLevels") describe("/users", () => { let request diff --git a/packages/server/src/api/routes/user.js b/packages/server/src/api/routes/user.js index 5289439e41..9394d842bd 100644 --- a/packages/server/src/api/routes/user.js +++ b/packages/server/src/api/routes/user.js @@ -1,19 +1,39 @@ const Router = require("@koa/router") const controller = require("../controllers/user") const authorized = require("../../middleware/authorized") -const { USER_MANAGEMENT, LIST_USERS } = require("../../utilities/accessLevels") +const { + PermissionLevels, + PermissionTypes, +} = require("../../utilities/security/permissions") const usage = require("../../middleware/usageQuota") const router = Router() router - .get("/api/users", authorized(LIST_USERS), controller.fetch) - .get("/api/users/:username", authorized(USER_MANAGEMENT), controller.find) - .put("/api/users/", authorized(USER_MANAGEMENT), controller.update) - .post("/api/users", authorized(USER_MANAGEMENT), usage, controller.create) + .get( + "/api/users", + authorized(PermissionTypes.USER, PermissionLevels.READ), + controller.fetch + ) + .get( + "/api/users/:username", + authorized(PermissionTypes.USER, PermissionLevels.READ), + controller.find + ) + .put( + "/api/users/", + authorized(PermissionTypes.USER, PermissionLevels.WRITE), + controller.update + ) + .post( + "/api/users", + authorized(PermissionTypes.USER, PermissionLevels.WRITE), + usage, + controller.create + ) .delete( "/api/users/:username", - authorized(USER_MANAGEMENT), + authorized(PermissionTypes.USER, PermissionLevels.WRITE), usage, controller.destroy ) diff --git a/packages/server/src/api/routes/view.js b/packages/server/src/api/routes/view.js index 3657a9e829..17277b346c 100644 --- a/packages/server/src/api/routes/view.js +++ b/packages/server/src/api/routes/view.js @@ -2,7 +2,11 @@ const Router = require("@koa/router") const viewController = require("../controllers/view") const rowController = require("../controllers/row") const authorized = require("../../middleware/authorized") -const { BUILDER, READ_VIEW } = require("../../utilities/accessLevels") +const { + BUILDER, + PermissionTypes, + PermissionLevels, +} = require("../../utilities/security/permissions") const usage = require("../../middleware/usageQuota") const router = Router() @@ -10,7 +14,7 @@ const router = Router() router .get( "/api/views/:viewName", - authorized(READ_VIEW, ctx => ctx.params.viewName), + authorized(PermissionTypes.VIEW, PermissionLevels.READ), rowController.fetchView ) .get("/api/views", authorized(BUILDER), viewController.fetch) diff --git a/packages/server/src/api/routes/webhook.js b/packages/server/src/api/routes/webhook.js index a7072904ed..fdcf14e490 100644 --- a/packages/server/src/api/routes/webhook.js +++ b/packages/server/src/api/routes/webhook.js @@ -2,7 +2,11 @@ const Router = require("@koa/router") const controller = require("../controllers/webhook") const authorized = require("../../middleware/authorized") const joiValidator = require("../../middleware/joi-validator") -const { BUILDER, EXECUTE_WEBHOOK } = require("../../utilities/accessLevels") +const { + BUILDER, + PermissionTypes, + PermissionLevels, +} = require("../../utilities/security/permissions") const Joi = require("joi") const router = Router() @@ -38,7 +42,7 @@ router ) .post( "/api/webhooks/trigger/:instance/:id", - authorized(EXECUTE_WEBHOOK), + authorized(PermissionTypes.WEBHOOK, PermissionLevels.EXECUTE), controller.trigger ) diff --git a/packages/server/src/automations/steps/createUser.js b/packages/server/src/automations/steps/createUser.js index 07d9f05316..d29144e01d 100644 --- a/packages/server/src/automations/steps/createUser.js +++ b/packages/server/src/automations/steps/createUser.js @@ -1,4 +1,4 @@ -const accessLevels = require("../../utilities/accessLevels") +const accessLevels = require("../../utilities/security/accessLevels") const userController = require("../../api/controllers/user") const env = require("../../environment") const usage = require("../../utilities/usageQuota") @@ -28,7 +28,7 @@ module.exports.definition = { accessLevelId: { type: "string", title: "Access Level", - enum: accessLevels.ACCESS_LEVELS, + enum: accessLevels.BUILTIN_LEVELS, pretty: Object.values(accessLevels.PRETTY_ACCESS_LEVELS), }, }, diff --git a/packages/server/src/middleware/authenticated.js b/packages/server/src/middleware/authenticated.js index 529581f362..c10f1387cf 100644 --- a/packages/server/src/middleware/authenticated.js +++ b/packages/server/src/middleware/authenticated.js @@ -2,11 +2,8 @@ const jwt = require("jsonwebtoken") const STATUS_CODES = require("../utilities/statusCodes") const accessLevelController = require("../api/controllers/accesslevel") const { - ADMIN_LEVEL_ID, - POWERUSER_LEVEL_ID, - BUILDER_LEVEL_ID, - ANON_LEVEL_ID, -} = require("../utilities/accessLevels") + BUILTIN_LEVEL_IDS, +} = require("../utilities/security/accessLevels") const env = require("../environment") const { AuthTypes } = require("../constants") const { getAppId, getCookieName, setCookie } = require("../utilities") @@ -74,12 +71,7 @@ module.exports = async (ctx, next) => { * @param {*} accessLevelId - the id of the users access level */ const getAccessLevel = async (appId, accessLevelId) => { - if ( - accessLevelId === POWERUSER_LEVEL_ID || - accessLevelId === ADMIN_LEVEL_ID || - accessLevelId === BUILDER_LEVEL_ID || - accessLevelId === ANON_LEVEL_ID - ) { + if (BUILTIN_LEVEL_IDS.indexOf(accessLevelId) !== -1) { return { _id: accessLevelId, name: accessLevelId, diff --git a/packages/server/src/middleware/authorized.js b/packages/server/src/middleware/authorized.js index 34758903f3..234c7d82fd 100644 --- a/packages/server/src/middleware/authorized.js +++ b/packages/server/src/middleware/authorized.js @@ -1,17 +1,14 @@ -const { - adminPermissions, - ADMIN_LEVEL_ID, - POWERUSER_LEVEL_ID, - BUILDER_LEVEL_ID, - BUILDER, -} = require("../utilities/accessLevels") +const { BUILTIN_LEVELS } = require("../utilities/security/accessLevels") +const { PermissionTypes } = require("../utilities/security/permissions") const env = require("../environment") const { apiKeyTable } = require("../db/dynamoClient") const { AuthTypes } = require("../constants") +const ADMIN_PERMS = [BUILTIN_LEVELS.admin._id, BUILTIN_LEVELS.builder._id] + const LOCAL_PASS = new RegExp(["webhooks/trigger", "webhooks/schema"].join("|")) -module.exports = (permName, getItemId) => async (ctx, next) => { +module.exports = (permType, permLevel = null) => async (ctx, next) => { // webhooks can pass locally if (!env.CLOUD && LOCAL_PASS.test(ctx.request.url)) { return next() @@ -37,7 +34,7 @@ module.exports = (permName, getItemId) => async (ctx, next) => { } // don't expose builder endpoints in the cloud - if (env.CLOUD && permName === BUILDER) return + if (env.CLOUD && permType === PermissionTypes.BUILDER) return if (!ctx.auth.authenticated) { ctx.throw(403, "Session not authenticated") @@ -47,41 +44,18 @@ module.exports = (permName, getItemId) => async (ctx, next) => { ctx.throw(403, "User not found") } - if (ctx.user.accessLevel._id === ADMIN_LEVEL_ID) { + if (ADMIN_PERMS.indexOf(ctx.user.accessLevel._id) !== -1) { return next() } - if (ctx.user.accessLevel._id === BUILDER_LEVEL_ID) { - return next() - } - - if (permName === BUILDER) { + if (permType === PermissionTypes.BUILDER) { ctx.throw(403, "Not Authorized") return } - const permissionId = ({ name, itemId }) => name + (itemId ? `-${itemId}` : "") + // TODO: Replace the old permissions system here, check whether + // user has permission to use endpoint they are trying to access + return next() - const thisPermissionId = permissionId({ - name: permName, - itemId: getItemId && getItemId(ctx), - }) - - // power user has everything, except the admin specific perms - if ( - ctx.user.accessLevel._id === POWERUSER_LEVEL_ID && - !adminPermissions.map(permissionId).includes(thisPermissionId) - ) { - return next() - } - - if ( - ctx.user.accessLevel.permissions - .map(permissionId) - .includes(thisPermissionId) - ) { - return next() - } - - ctx.throw(403, "Not Authorized") + //ctx.throw(403, "Not Authorized") } diff --git a/packages/server/src/utilities/accessLevels.js b/packages/server/src/utilities/accessLevels.js deleted file mode 100644 index e38a7cf23f..0000000000 --- a/packages/server/src/utilities/accessLevels.js +++ /dev/null @@ -1,36 +0,0 @@ -// Permissions -module.exports.READ_TABLE = "read-table" -module.exports.WRITE_TABLE = "write-table" -module.exports.READ_VIEW = "read-view" -module.exports.EXECUTE_AUTOMATION = "execute-automation" -module.exports.EXECUTE_WEBHOOK = "execute-webhook" -module.exports.USER_MANAGEMENT = "user-management" -module.exports.BUILDER = "builder" -module.exports.LIST_USERS = "list-users" -// Access Level IDs -module.exports.ADMIN_LEVEL_ID = "ADMIN" -module.exports.POWERUSER_LEVEL_ID = "POWER_USER" -module.exports.BUILDER_LEVEL_ID = "BUILDER" -module.exports.ANON_LEVEL_ID = "ANON" -module.exports.ACCESS_LEVELS = [ - module.exports.ADMIN_LEVEL_ID, - module.exports.POWERUSER_LEVEL_ID, - module.exports.BUILDER_LEVEL_ID, - module.exports.ANON_LEVEL_ID, -] -module.exports.PRETTY_ACCESS_LEVELS = { - [module.exports.ADMIN_LEVEL_ID]: "Admin", - [module.exports.POWERUSER_LEVEL_ID]: "Power user", - [module.exports.BUILDER_LEVEL_ID]: "Builder", -} -module.exports.adminPermissions = [ - { - name: module.exports.USER_MANAGEMENT, - }, -] - -// to avoid circular dependencies this is included later, after exporting all enums -const permissions = require("./permissions") -module.exports.generateAdminPermissions = permissions.generateAdminPermissions -module.exports.generatePowerUserPermissions = - permissions.generatePowerUserPermissions diff --git a/packages/server/src/utilities/builder/setBuilderToken.js b/packages/server/src/utilities/builder/setBuilderToken.js index 8cf6c44379..56969f7d3a 100644 --- a/packages/server/src/utilities/builder/setBuilderToken.js +++ b/packages/server/src/utilities/builder/setBuilderToken.js @@ -1,4 +1,4 @@ -const { BUILDER_LEVEL_ID } = require("../accessLevels") +const { BUILDER_LEVEL_ID } = require("../security/accessLevels") const env = require("../../environment") const CouchDB = require("../../db") const jwt = require("jsonwebtoken") diff --git a/packages/server/src/utilities/permissions.js b/packages/server/src/utilities/permissions.js index e1513fd0fa..3a161f6405 100644 --- a/packages/server/src/utilities/permissions.js +++ b/packages/server/src/utilities/permissions.js @@ -1,7 +1,7 @@ const viewController = require("../api/controllers/view") const tableController = require("../api/controllers/table") const automationController = require("../api/controllers/automation") -const accessLevels = require("./accessLevels") +const accessLevels = require("./security/accessLevels") // this has been broken out to reduce risk of circular dependency from utilities, no enums defined here const generateAdminPermissions = async appId => [ diff --git a/packages/server/src/utilities/security/accessLevels.js b/packages/server/src/utilities/security/accessLevels.js new file mode 100644 index 0000000000..77a2955261 --- /dev/null +++ b/packages/server/src/utilities/security/accessLevels.js @@ -0,0 +1,44 @@ +const { DocumentTypes, SEPARATOR } = require("../../db/utils") + +function makeAccessLevelId(baseId) { + return `${DocumentTypes.ACCESS_LEVEL}${SEPARATOR}${baseId}` +} + +// Permissions +exports.READ_TABLE = "read-table" +exports.WRITE_TABLE = "write-table" +exports.READ_VIEW = "read-view" +exports.EXECUTE_AUTOMATION = "execute-automation" +exports.EXECUTE_WEBHOOK = "execute-webhook" +exports.USER_MANAGEMENT = "user-management" +exports.BUILDER = "builder" +exports.LIST_USERS = "list-users" +// Access Level IDs +exports.ADMIN_LEVEL_ID = "ADMIN" +exports.POWERUSER_LEVEL_ID = "POWER_USER" +exports.BUILDER_LEVEL_ID = "BUILDER" +exports.ANON_LEVEL_ID = "ANON" +exports.BUILTIN_LEVELS = { + admin: { _id: makeAccessLevelId("ADMIN"), name: "Admin" }, + power: { _id: makeAccessLevelId("POWER_USER"), name: "Power user" }, + builder: { _id: makeAccessLevelId("BUILDER"), name: "Builder" }, + anon: { _id: makeAccessLevelId("ANON"), name: "Anonymous" }, +} +exports.BUILTIN_LEVEL_IDS = Object.values(exports.BUILTIN_LEVELS).map( + level => level._id +) +exports.PRETTY_ACCESS_LEVELS = { + [exports.ADMIN_LEVEL_ID]: "Admin", + [exports.POWERUSER_LEVEL_ID]: "Power user", + [exports.BUILDER_LEVEL_ID]: "Builder", +} +exports.adminPermissions = [ + { + name: exports.USER_MANAGEMENT, + }, +] + +// to avoid circular dependencies this is included later, after exporting all enums +const permissions = require("../permissions") +exports.generateAdminPermissions = permissions.generateAdminPermissions +exports.generatePowerUserPermissions = permissions.generatePowerUserPermissions diff --git a/packages/server/src/utilities/security/permissions.js b/packages/server/src/utilities/security/permissions.js new file mode 100644 index 0000000000..7a230611c0 --- /dev/null +++ b/packages/server/src/utilities/security/permissions.js @@ -0,0 +1,101 @@ +const { flatten } = require("lodash") + +exports.READ_TABLE = "read-table" +exports.WRITE_TABLE = "write-table" +exports.READ_VIEW = "read-view" +exports.EXECUTE_AUTOMATION = "execute-automation" +exports.EXECUTE_WEBHOOK = "execute-webhook" +exports.USER_MANAGEMENT = "user-management" +exports.BUILDER = "builder" +exports.LIST_USERS = "list-users" + +const PermissionLevels = { + READ: "read", + WRITE: "write", + EXECUTE: "execute", + ADMIN: "admin", +} + +const PermissionTypes = { + TABLE: "table", + USER: "user", + AUTOMATION: "automation", + WEBHOOK: "webhook", + BUILDER: "builder", + VIEW: "view", +} + +function Permission(type, level) { + this.level = level + this.type = type +} + +/** + * Given the specified permission level for the user return the levels they are allowed to carry out. + * @param {string} userPermLevel The permission level of the user. + * @return {string[]} All the permission levels this user is allowed to carry out. + */ +function getAllowedLevels(userPermLevel) { + switch (userPermLevel) { + case PermissionLevels.READ: + return [PermissionLevels.READ] + case PermissionLevels.WRITE: + return [PermissionLevels.READ, PermissionLevels.WRITE] + case PermissionLevels.EXECUTE: + return [PermissionLevels.EXECUTE] + case PermissionLevels.ADMIN: + return [ + PermissionLevels.READ, + PermissionLevels.WRITE, + PermissionLevels.EXECUTE, + ] + default: + return [] + } +} + +// TODO: need to expand on this +exports.BUILTIN_PERMISSION_NAMES = { + READ_ONLY: "read_only", + WRITE: "write", +} + +exports.BUILTIN_PERMISSIONS = { + READ_ONLY: { + name: exports.BUILTIN_PERMISSION_NAMES.READ_ONLY, + permissions: [ + new Permission(PermissionTypes.TABLE, PermissionLevels.READ), + new Permission(PermissionTypes.VIEW, PermissionLevels.READ), + ], + }, + WRITE: { + name: exports.BUILTIN_PERMISSION_NAMES.WRITE, + permissions: [ + new Permission(PermissionTypes.TABLE, PermissionLevels.WRITE), + new Permission(PermissionTypes.VIEW, PermissionLevels.READ), + ], + }, +} + +exports.doesHavePermission = (permType, permLevel, userPermissionNames) => { + const builtins = Object.values(exports.BUILTIN_PERMISSIONS) + let permissions = flatten( + builtins + .filter(builtin => userPermissionNames.indexOf(builtin.name) !== -1) + .map(builtin => builtin.permissions) + ) + for (let permission of permissions) { + if ( + permission.type === permType && + getAllowedLevels(permission.level).indexOf(permLevel) !== -1 + ) { + return true + } + } + return false +} + +// utility as a lot of things need simply the builder permission +exports.BUILDER = PermissionTypes.BUILDER +exports.PermissionTypes = PermissionTypes +exports.PermissionLevels = PermissionLevels From 61481285540400f68e0d4ea249e7ba2ecf852962 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Thu, 12 Nov 2020 17:06:55 +0000 Subject: [PATCH 03/15] Tests failing but starting to progress. --- .../server/src/api/controllers/accesslevel.js | 24 ++----- packages/server/src/api/controllers/auth.js | 1 + .../server/src/api/controllers/routing.js | 3 + packages/server/src/api/controllers/user.js | 16 +++-- packages/server/src/api/index.js | 4 ++ packages/server/src/api/routes/index.js | 2 + packages/server/src/api/routes/routing.js | 10 +++ .../src/api/routes/tests/accesslevel.spec.js | 26 ++++---- .../src/api/routes/tests/couchTestUtils.js | 51 +++++--------- .../server/src/api/routes/tests/user.spec.js | 17 ++--- .../src/automations/steps/createUser.js | 4 +- packages/server/src/middleware/authorized.js | 20 ++++-- .../src/utilities/builder/setBuilderToken.js | 6 +- packages/server/src/utilities/permissions.js | 66 ------------------- .../src/utilities/security/accessLevels.js | 46 +++---------- .../src/utilities/security/permissions.js | 32 ++++++--- 16 files changed, 124 insertions(+), 204 deletions(-) create mode 100644 packages/server/src/api/controllers/routing.js create mode 100644 packages/server/src/api/routes/routing.js delete mode 100644 packages/server/src/utilities/permissions.js diff --git a/packages/server/src/api/controllers/accesslevel.js b/packages/server/src/api/controllers/accesslevel.js index 8c525ba52a..1a19309333 100644 --- a/packages/server/src/api/controllers/accesslevel.js +++ b/packages/server/src/api/controllers/accesslevel.js @@ -1,9 +1,8 @@ const CouchDB = require("../../db") +const { BUILTIN_LEVELS } = require("../../utilities/security/accessLevels") const { - generateAdminPermissions, - generatePowerUserPermissions, - BUILTIN_LEVELS, -} = require("../../utilities/security/accessLevels") + BUILTIN_PERMISSION_NAMES, +} = require("../../utilities/security/permissions") const { generateAccessLevelID, getAccessLevelParams, @@ -21,11 +20,11 @@ exports.fetch = async function(ctx) { const staticAccessLevels = [ { ...BUILTIN_LEVELS.admin, - permissions: await generateAdminPermissions(ctx.user.appId), + permissions: [BUILTIN_PERMISSION_NAMES.ADMIN], }, { ...BUILTIN_LEVELS.power, - permissions: await generatePowerUserPermissions(ctx.user.appId), + permissions: [BUILTIN_PERMISSION_NAMES.POWER], }, ] @@ -59,22 +58,13 @@ exports.patch = async function(ctx) { if (removedPermissions) { level.permissions = level.permissions.filter( - p => - !removedPermissions.some( - rem => rem.name === p.name && rem.itemId === p.itemId - ) + permission => removedPermissions.indexOf(permission) === -1 ) } if (addedPermissions) { level.permissions = [ - ...level.permissions.filter( - p => - !addedPermissions.some( - add => add.name === p.name && add.itemId === p.itemId - ) - ), - ...addedPermissions, + ...new Set([...addedPermissions, ...level.permissions]), ] } diff --git a/packages/server/src/api/controllers/auth.js b/packages/server/src/api/controllers/auth.js index 2c162587b3..21136b0214 100644 --- a/packages/server/src/api/controllers/auth.js +++ b/packages/server/src/api/controllers/auth.js @@ -34,6 +34,7 @@ exports.authenticate = async ctx => { userId: dbUser._id, accessLevelId: dbUser.accessLevelId, version: app.version, + permissions: dbUser.permissions || [], } // if in cloud add the user api key if (env.CLOUD) { diff --git a/packages/server/src/api/controllers/routing.js b/packages/server/src/api/controllers/routing.js new file mode 100644 index 0000000000..ae3f949833 --- /dev/null +++ b/packages/server/src/api/controllers/routing.js @@ -0,0 +1,3 @@ +exports.fetch = async ctx => { + +} \ No newline at end of file diff --git a/packages/server/src/api/controllers/user.js b/packages/server/src/api/controllers/user.js index 7d460c1db6..08044d4754 100644 --- a/packages/server/src/api/controllers/user.js +++ b/packages/server/src/api/controllers/user.js @@ -1,9 +1,10 @@ const CouchDB = require("../../db") const bcrypt = require("../../utilities/bcrypt") const { generateUserID, getUserParams } = require("../../db/utils") +const { BUILTIN_LEVEL_IDS } = require("../../utilities/security/accessLevels") const { - BUILTIN_LEVELS_IDS, -} = require("../../utilities/security/accessLevels") + BUILTIN_PERMISSION_NAMES, +} = require("../../utilities/security/permissions") exports.fetch = async function(ctx) { const database = new CouchDB(ctx.user.appId) @@ -17,7 +18,13 @@ exports.fetch = async function(ctx) { exports.create = async function(ctx) { const db = new CouchDB(ctx.user.appId) - const { username, password, name, accessLevelId } = ctx.request.body + const { + username, + password, + name, + accessLevelId, + permissions, + } = ctx.request.body if (!username || !password) { ctx.throw(400, "Username and Password Required.") @@ -34,6 +41,7 @@ exports.create = async function(ctx) { name: name || username, type: "user", accessLevelId, + permissions: permissions || [BUILTIN_PERMISSION_NAMES.POWER], } try { @@ -88,7 +96,7 @@ exports.find = async function(ctx) { const checkAccessLevel = async (db, accessLevelId) => { if (!accessLevelId) return - if (BUILTIN_LEVELS_IDS.indexOf(accessLevelId) !== -1) { + if (BUILTIN_LEVEL_IDS.indexOf(accessLevelId) !== -1) { return { _id: accessLevelId, name: accessLevelId, diff --git a/packages/server/src/api/index.js b/packages/server/src/api/index.js index 4a4e80d1ed..0d25e08dee 100644 --- a/packages/server/src/api/index.js +++ b/packages/server/src/api/index.js @@ -22,6 +22,7 @@ const { templatesRoutes, analyticsRoutes, webhookRoutes, + routingRoutes, } = require("./routes") const router = new Router() @@ -121,6 +122,9 @@ router.use(analyticsRoutes.allowedMethods()) router.use(staticRoutes.routes()) router.use(staticRoutes.allowedMethods()) +router.use(routingRoutes.routes()) +router.use(routingRoutes.allowedMethods()) + router.redirect("/", "/_builder") module.exports = router diff --git a/packages/server/src/api/routes/index.js b/packages/server/src/api/routes/index.js index a19742097c..2352b2edc2 100644 --- a/packages/server/src/api/routes/index.js +++ b/packages/server/src/api/routes/index.js @@ -15,6 +15,7 @@ const deployRoutes = require("./deploy") const apiKeysRoutes = require("./apikeys") const templatesRoutes = require("./templates") const analyticsRoutes = require("./analytics") +const routingRoutes = require("./routing") module.exports = { deployRoutes, @@ -34,4 +35,5 @@ module.exports = { templatesRoutes, analyticsRoutes, webhookRoutes, + routingRoutes, } diff --git a/packages/server/src/api/routes/routing.js b/packages/server/src/api/routes/routing.js new file mode 100644 index 0000000000..f336e4ed67 --- /dev/null +++ b/packages/server/src/api/routes/routing.js @@ -0,0 +1,10 @@ +const Router = require("@koa/router") +const authorized = require("../../middleware/authorized") +const { BUILDER } = require("../../utilities/security/permissions") +const controller = require("../controllers/routing") + +const router = Router() + +router.post("/api/routing", authorized(BUILDER), controller.fetch) + +module.exports = router diff --git a/packages/server/src/api/routes/tests/accesslevel.spec.js b/packages/server/src/api/routes/tests/accesslevel.spec.js index 2fc4b54e20..01ca97664a 100644 --- a/packages/server/src/api/routes/tests/accesslevel.spec.js +++ b/packages/server/src/api/routes/tests/accesslevel.spec.js @@ -6,11 +6,7 @@ const { defaultHeaders } = require("./couchTestUtils") const { - generateAdminPermissions, - generatePowerUserPermissions, BUILTIN_LEVELS, - READ_TABLE, - WRITE_TABLE, } = require("../../../utilities/security/accessLevels") const { BUILTIN_PERMISSION_NAMES } = require("../../../utilities/security/permissions") @@ -76,14 +72,14 @@ describe("/accesslevels", () => { const adminLevel = res.body.find(r => r._id === BUILTIN_LEVELS.admin._id) expect(adminLevel).toBeDefined() - expect(adminLevel.permissions).toEqual(await generateAdminPermissions(appId)) + expect(adminLevel.permissions).toEqual([BUILTIN_PERMISSION_NAMES.ADMIN]) const powerUserLevel = res.body.find(r => r._id === BUILTIN_LEVELS.power._id) expect(powerUserLevel).toBeDefined() - expect(powerUserLevel.permissions).toEqual(await generatePowerUserPermissions(appId)) + expect(powerUserLevel.permissions).toEqual([BUILTIN_PERMISSION_NAMES.POWER]) const customLevelFetched = res.body.find(r => r._id === customLevel._id) - expect(customLevelFetched.permissions).toEqual(customLevel.permissions) + expect(customLevelFetched.permissions).toEqual([BUILTIN_PERMISSION_NAMES.READ_ONLY]) }) }); @@ -126,7 +122,7 @@ describe("/accesslevels", () => { .patch(`/api/accesslevels/${customLevel._id}`) .send({ _rev: customLevel._rev, - addedPermissions: [ { itemId: table._id, name: WRITE_TABLE } ] + addedPermissions: [ BUILTIN_PERMISSION_NAMES.WRITE ] }) .set(defaultHeaders(appId)) .expect('Content-Type', /json/) @@ -138,8 +134,8 @@ describe("/accesslevels", () => { .expect(200) expect(finalRes.body.permissions.length).toBe(2) - expect(finalRes.body.permissions.some(p => p.name === WRITE_TABLE)).toBe(true) - expect(finalRes.body.permissions.some(p => p.name === READ_TABLE)).toBe(true) + expect(finalRes.body.permissions.indexOf(BUILTIN_PERMISSION_NAMES.WRITE)).not.toBe(-1) + expect(finalRes.body.permissions.indexOf(BUILTIN_PERMISSION_NAMES.READ_ONLY)).not.toBe(-1) }) it("should remove given permissions", async () => { @@ -147,9 +143,9 @@ describe("/accesslevels", () => { .post(`/api/accesslevels`) .send({ name: "user", - permissions: [ - { itemId: table._id, name: READ_TABLE }, - { itemId: table._id, name: WRITE_TABLE }, + permissions: [ + BUILTIN_PERMISSION_NAMES.READ_ONLY, + BUILTIN_PERMISSION_NAMES.WRITE, ] }) .set(defaultHeaders(appId)) @@ -162,7 +158,7 @@ describe("/accesslevels", () => { .patch(`/api/accesslevels/${customLevel._id}`) .send({ _rev: customLevel._rev, - removedPermissions: [ { itemId: table._id, name: WRITE_TABLE }] + removedPermissions: [BUILTIN_PERMISSION_NAMES.WRITE] }) .set(defaultHeaders(appId)) .expect('Content-Type', /json/) @@ -174,7 +170,7 @@ describe("/accesslevels", () => { .expect(200) expect(finalRes.body.permissions.length).toBe(1) - expect(finalRes.body.permissions.some(p => p.name === READ_TABLE)).toBe(true) + expect(finalRes.body.permissions.indexOf(BUILTIN_PERMISSION_NAMES.READ_ONLY)).not.toBe(-1) }) }) }); diff --git a/packages/server/src/api/routes/tests/couchTestUtils.js b/packages/server/src/api/routes/tests/couchTestUtils.js index bbb2da903e..a3d01bfc6b 100644 --- a/packages/server/src/api/routes/tests/couchTestUtils.js +++ b/packages/server/src/api/routes/tests/couchTestUtils.js @@ -1,11 +1,11 @@ const CouchDB = require("../../../db") const supertest = require("supertest") const { - POWERUSER_LEVEL_ID, - ANON_LEVEL_ID, - BUILDER_LEVEL_ID, - generateAdminPermissions, + BUILTIN_LEVELS, } = require("../../../utilities/security/accessLevels") +const { + BUILTIN_PERMISSION_NAMES, +} = require("../../../utilities/security/permissions") const packageJson = require("../../../../package") const jwt = require("jsonwebtoken") const env = require("../../../environment") @@ -26,7 +26,7 @@ exports.supertest = async () => { exports.defaultHeaders = appId => { const builderUser = { userId: "BUILDER", - accessLevelId: BUILDER_LEVEL_ID, + accessLevelId: BUILTIN_LEVELS.builder._id, } const builderToken = jwt.sign(builderUser, env.JWT_SECRET) @@ -126,21 +126,13 @@ exports.createUser = async ( name: "Bill", username, password, - accessLevelId: POWERUSER_LEVEL_ID, + accessLevelId: BUILTIN_LEVELS.power._id, }) return res.body } -const createUserWithOnePermission = async ( - request, - appId, - permName, - itemId -) => { - let permissions = await generateAdminPermissions(appId) - permissions = permissions.filter( - p => p.name === permName && p.itemId === itemId - ) +const createUserWithOnePermission = async (request, appId, permName) => { + let permissions = [permName] return await createUserWithPermissions( request, @@ -151,7 +143,7 @@ const createUserWithOnePermission = async ( } const createUserWithAdminPermissions = async (request, appId) => { - let permissions = await generateAdminPermissions(appId) + let permissions = [BUILTIN_PERMISSION_NAMES.ADMIN] return await createUserWithPermissions( request, @@ -164,13 +156,9 @@ const createUserWithAdminPermissions = async (request, appId) => { const createUserWithAllPermissionExceptOne = async ( request, appId, - permName, - itemId + permName ) => { - let permissions = await generateAdminPermissions(appId) - permissions = permissions.filter( - p => !(p.name === permName && p.itemId === itemId) - ) + let permissions = [permName] return await createUserWithPermissions( request, @@ -186,11 +174,6 @@ const createUserWithPermissions = async ( permissions, username ) => { - const accessRes = await request - .post(`/api/accesslevels`) - .send({ name: "TestLevel", permissions }) - .set(exports.defaultHeaders(appId)) - const password = `password_${username}` await request .post(`/api/users`) @@ -199,12 +182,13 @@ const createUserWithPermissions = async ( name: username, username, password, - accessLevelId: accessRes.body._id, + accessLevelId: BUILTIN_LEVELS.power._id, + permissions, }) const anonUser = { userId: "ANON", - accessLevelId: ANON_LEVEL_ID, + accessLevelId: BUILTIN_LEVELS.anon._id, appId: appId, version: packageJson.version, } @@ -233,13 +217,11 @@ exports.testPermissionsForEndpoint = async ({ body, appId, permissionName, - itemId, }) => { const headers = await createUserWithOnePermission( request, appId, - permissionName, - itemId + permissionName ) await createRequest(request, method, url, body) @@ -249,8 +231,7 @@ exports.testPermissionsForEndpoint = async ({ const noPermsHeaders = await createUserWithAllPermissionExceptOne( request, appId, - permissionName, - itemId + permissionName ) await createRequest(request, method, url, body) diff --git a/packages/server/src/api/routes/tests/user.spec.js b/packages/server/src/api/routes/tests/user.spec.js index d0c12f2ea1..bd77c179b1 100644 --- a/packages/server/src/api/routes/tests/user.spec.js +++ b/packages/server/src/api/routes/tests/user.spec.js @@ -5,10 +5,11 @@ const { createUser, testPermissionsForEndpoint, } = require("./couchTestUtils") -const { - POWERUSER_LEVEL_ID, - LIST_USERS, - USER_MANAGEMENT +const { + BUILTIN_PERMISSION_NAMES, +} = require("../../../utilities/security/permissions") +const { + BUILTIN_LEVELS } = require("../../../utilities/security/accessLevels") describe("/users", () => { @@ -53,7 +54,7 @@ describe("/users", () => { method: "GET", url: `/api/users`, appId: appId, - permissionName: LIST_USERS, + permissionName: BUILTIN_PERMISSION_NAMES.WRITE, }) }) @@ -65,7 +66,7 @@ describe("/users", () => { const res = await request .post(`/api/users`) .set(defaultHeaders(appId)) - .send({ name: "Bill", username: "bill", password: "bills_password", accessLevelId: POWERUSER_LEVEL_ID }) + .send({ name: "Bill", username: "bill", password: "bills_password", accessLevelId: BUILTIN_LEVELS.power._id }) .expect(200) .expect('Content-Type', /json/) @@ -77,10 +78,10 @@ describe("/users", () => { await testPermissionsForEndpoint({ request, method: "POST", - body: { name: "brandNewUser", username: "brandNewUser", password: "yeeooo", accessLevelId: POWERUSER_LEVEL_ID }, + body: { name: "brandNewUser", username: "brandNewUser", password: "yeeooo", accessLevelId: BUILTIN_LEVELS.power._id }, url: `/api/users`, appId: appId, - permissionName: USER_MANAGEMENT, + permissionName: BUILTIN_PERMISSION_NAMES.WRITE, }) }) diff --git a/packages/server/src/automations/steps/createUser.js b/packages/server/src/automations/steps/createUser.js index d29144e01d..40f7d76a38 100644 --- a/packages/server/src/automations/steps/createUser.js +++ b/packages/server/src/automations/steps/createUser.js @@ -11,7 +11,7 @@ module.exports.definition = { type: "ACTION", stepId: "CREATE_USER", inputs: { - accessLevelId: accessLevels.POWERUSER_LEVEL_ID, + accessLevelId: accessLevels.BUILTIN_LEVELS.power._id, }, schema: { inputs: { @@ -29,7 +29,7 @@ module.exports.definition = { type: "string", title: "Access Level", enum: accessLevels.BUILTIN_LEVELS, - pretty: Object.values(accessLevels.PRETTY_ACCESS_LEVELS), + pretty: accessLevels.BUILTIN_LEVEL_NAMES, }, }, required: ["username", "password", "accessLevelId"], diff --git a/packages/server/src/middleware/authorized.js b/packages/server/src/middleware/authorized.js index 234c7d82fd..93ad57d071 100644 --- a/packages/server/src/middleware/authorized.js +++ b/packages/server/src/middleware/authorized.js @@ -1,5 +1,8 @@ const { BUILTIN_LEVELS } = require("../utilities/security/accessLevels") -const { PermissionTypes } = require("../utilities/security/permissions") +const { + PermissionTypes, + doesHavePermission, +} = require("../utilities/security/permissions") const env = require("../environment") const { apiKeyTable } = require("../db/dynamoClient") const { AuthTypes } = require("../constants") @@ -44,18 +47,21 @@ module.exports = (permType, permLevel = null) => async (ctx, next) => { ctx.throw(403, "User not found") } - if (ADMIN_PERMS.indexOf(ctx.user.accessLevel._id) !== -1) { + const accessLevel = ctx.user.accessLevel + const permissions = ctx.user.permissions + if (ADMIN_PERMS.indexOf(accessLevel._id) !== -1) { return next() } + // TODO: need to handle routing security + if (permType === PermissionTypes.BUILDER) { ctx.throw(403, "Not Authorized") - return } - // TODO: Replace the old permissions system here, check whether - // user has permission to use endpoint they are trying to access - return next() + if (!doesHavePermission(permType, permLevel, permissions)) { + ctx.throw(403, "User does not have permission") + } - //ctx.throw(403, "Not Authorized") + return next() } diff --git a/packages/server/src/utilities/builder/setBuilderToken.js b/packages/server/src/utilities/builder/setBuilderToken.js index 56969f7d3a..982fe9ad09 100644 --- a/packages/server/src/utilities/builder/setBuilderToken.js +++ b/packages/server/src/utilities/builder/setBuilderToken.js @@ -1,4 +1,5 @@ -const { BUILDER_LEVEL_ID } = require("../security/accessLevels") +const { BUILTIN_LEVELS } = require("../security/accessLevels") +const { BUILTIN_PERMISSION_NAMES } = require("../security/permissions") const env = require("../../environment") const CouchDB = require("../../db") const jwt = require("jsonwebtoken") @@ -9,7 +10,8 @@ const APP_PREFIX = DocumentTypes.APP + SEPARATOR module.exports = async (ctx, appId, version) => { const builderUser = { userId: "BUILDER", - accessLevelId: BUILDER_LEVEL_ID, + accessLevelId: BUILTIN_LEVELS.builder._id, + permissions: [BUILTIN_PERMISSION_NAMES.ADMIN], version, } if (env.BUDIBASE_API_KEY) { diff --git a/packages/server/src/utilities/permissions.js b/packages/server/src/utilities/permissions.js deleted file mode 100644 index 3a161f6405..0000000000 --- a/packages/server/src/utilities/permissions.js +++ /dev/null @@ -1,66 +0,0 @@ -const viewController = require("../api/controllers/view") -const tableController = require("../api/controllers/table") -const automationController = require("../api/controllers/automation") -const accessLevels = require("./security/accessLevels") - -// this has been broken out to reduce risk of circular dependency from utilities, no enums defined here -const generateAdminPermissions = async appId => [ - ...accessLevels.adminPermissions, - ...(await generatePowerUserPermissions(appId)), -] - -const generatePowerUserPermissions = async appId => { - const fetchTablesCtx = { - user: { - appId, - }, - } - await tableController.fetch(fetchTablesCtx) - const tables = fetchTablesCtx.body - - const fetchViewsCtx = { - user: { - appId, - }, - } - await viewController.fetch(fetchViewsCtx) - const views = fetchViewsCtx.body - - const fetchAutomationsCtx = { - user: { - appId, - }, - } - await automationController.fetch(fetchAutomationsCtx) - const automations = fetchAutomationsCtx.body - - const readTablePermissions = tables.map(m => ({ - itemId: m._id, - name: accessLevels.READ_TABLE, - })) - - const writeTablePermissions = tables.map(m => ({ - itemId: m._id, - name: accessLevels.WRITE_TABLE, - })) - - const viewPermissions = views.map(v => ({ - itemId: v.name, - name: accessLevels.READ_VIEW, - })) - - const executeAutomationPermissions = automations.map(w => ({ - itemId: w._id, - name: accessLevels.EXECUTE_AUTOMATION, - })) - - return [ - ...readTablePermissions, - ...writeTablePermissions, - ...viewPermissions, - ...executeAutomationPermissions, - { name: accessLevels.LIST_USERS }, - ] -} -module.exports.generateAdminPermissions = generateAdminPermissions -module.exports.generatePowerUserPermissions = generatePowerUserPermissions diff --git a/packages/server/src/utilities/security/accessLevels.js b/packages/server/src/utilities/security/accessLevels.js index 77a2955261..48e088afb5 100644 --- a/packages/server/src/utilities/security/accessLevels.js +++ b/packages/server/src/utilities/security/accessLevels.js @@ -1,44 +1,14 @@ -const { DocumentTypes, SEPARATOR } = require("../../db/utils") - -function makeAccessLevelId(baseId) { - return `${DocumentTypes.ACCESS_LEVEL}${SEPARATOR}${baseId}` -} - -// Permissions -exports.READ_TABLE = "read-table" -exports.WRITE_TABLE = "write-table" -exports.READ_VIEW = "read-view" -exports.EXECUTE_AUTOMATION = "execute-automation" -exports.EXECUTE_WEBHOOK = "execute-webhook" -exports.USER_MANAGEMENT = "user-management" -exports.BUILDER = "builder" -exports.LIST_USERS = "list-users" -// Access Level IDs -exports.ADMIN_LEVEL_ID = "ADMIN" -exports.POWERUSER_LEVEL_ID = "POWER_USER" -exports.BUILDER_LEVEL_ID = "BUILDER" -exports.ANON_LEVEL_ID = "ANON" exports.BUILTIN_LEVELS = { - admin: { _id: makeAccessLevelId("ADMIN"), name: "Admin" }, - power: { _id: makeAccessLevelId("POWER_USER"), name: "Power user" }, - builder: { _id: makeAccessLevelId("BUILDER"), name: "Builder" }, - anon: { _id: makeAccessLevelId("ANON"), name: "Anonymous" }, + admin: { _id: "ADMIN", name: "Admin" }, + power: { _id: "POWER_USER", name: "Power user" }, + builder: { _id: "BUILDER", name: "Builder" }, + anon: { _id: "ANON", name: "Anonymous" }, } + exports.BUILTIN_LEVEL_IDS = Object.values(exports.BUILTIN_LEVELS).map( level => level._id ) -exports.PRETTY_ACCESS_LEVELS = { - [exports.ADMIN_LEVEL_ID]: "Admin", - [exports.POWERUSER_LEVEL_ID]: "Power user", - [exports.BUILDER_LEVEL_ID]: "Builder", -} -exports.adminPermissions = [ - { - name: exports.USER_MANAGEMENT, - }, -] -// to avoid circular dependencies this is included later, after exporting all enums -const permissions = require("../permissions") -exports.generateAdminPermissions = permissions.generateAdminPermissions -exports.generatePowerUserPermissions = permissions.generatePowerUserPermissions +exports.BUILTIN_LEVEL_NAMES = Object.values(exports.BUILTIN_LEVELS).map( + level => level.name +) diff --git a/packages/server/src/utilities/security/permissions.js b/packages/server/src/utilities/security/permissions.js index 7a230611c0..d19f31e393 100644 --- a/packages/server/src/utilities/security/permissions.js +++ b/packages/server/src/utilities/security/permissions.js @@ -1,14 +1,5 @@ const { flatten } = require("lodash") -exports.READ_TABLE = "read-table" -exports.WRITE_TABLE = "write-table" -exports.READ_VIEW = "read-view" -exports.EXECUTE_AUTOMATION = "execute-automation" -exports.EXECUTE_WEBHOOK = "execute-webhook" -exports.USER_MANAGEMENT = "user-management" -exports.BUILDER = "builder" -exports.LIST_USERS = "list-users" - const PermissionLevels = { READ: "read", WRITE: "write", @@ -54,10 +45,11 @@ function getAllowedLevels(userPermLevel) { } } -// TODO: need to expand on this exports.BUILTIN_PERMISSION_NAMES = { READ_ONLY: "read_only", WRITE: "write", + ADMIN: "admin", + POWER: "power", } exports.BUILTIN_PERMISSIONS = { @@ -75,6 +67,26 @@ exports.BUILTIN_PERMISSIONS = { new Permission(PermissionTypes.VIEW, PermissionLevels.READ), ], }, + POWER: { + name: exports.BUILTIN_PERMISSION_NAMES.POWER, + permissions: [ + new Permission(PermissionTypes.TABLE, PermissionLevels.WRITE), + new Permission(PermissionTypes.USER, PermissionLevels.READ), + new Permission(PermissionTypes.AUTOMATION, PermissionLevels.EXECUTE), + new Permission(PermissionTypes.VIEW, PermissionLevels.READ), + new Permission(PermissionTypes.WEBHOOK, PermissionLevels.READ), + ], + }, + ADMIN: { + name: exports.BUILTIN_PERMISSION_NAMES.ADMIN, + permissions: [ + new Permission(PermissionTypes.TABLE, PermissionLevels.ADMIN), + new Permission(PermissionTypes.USER, PermissionLevels.ADMIN), + new Permission(PermissionTypes.AUTOMATION, PermissionLevels.ADMIN), + new Permission(PermissionTypes.VIEW, PermissionLevels.ADMIN), + new Permission(PermissionTypes.WEBHOOK, PermissionLevels.READ), + ], + }, } exports.doesHavePermission = (permType, permLevel, userPermissionNames) => { From b6ea9440de9c7904546b8db07044fed0142f9c49 Mon Sep 17 00:00:00 2001 From: Michael Drury Date: Thu, 12 Nov 2020 17:41:50 +0000 Subject: [PATCH 04/15] Fixing test case. --- .../server/src/api/routes/tests/couchTestUtils.js | 11 ++++------- packages/server/src/api/routes/tests/user.spec.js | 6 ++++-- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/packages/server/src/api/routes/tests/couchTestUtils.js b/packages/server/src/api/routes/tests/couchTestUtils.js index a3d01bfc6b..ec86c8b707 100644 --- a/packages/server/src/api/routes/tests/couchTestUtils.js +++ b/packages/server/src/api/routes/tests/couchTestUtils.js @@ -216,13 +216,10 @@ exports.testPermissionsForEndpoint = async ({ url, body, appId, - permissionName, + permName1, + permName2, }) => { - const headers = await createUserWithOnePermission( - request, - appId, - permissionName - ) + const headers = await createUserWithOnePermission(request, appId, permName1) await createRequest(request, method, url, body) .set(headers) @@ -231,7 +228,7 @@ exports.testPermissionsForEndpoint = async ({ const noPermsHeaders = await createUserWithAllPermissionExceptOne( request, appId, - permissionName + permName2 ) await createRequest(request, method, url, body) diff --git a/packages/server/src/api/routes/tests/user.spec.js b/packages/server/src/api/routes/tests/user.spec.js index bd77c179b1..2862903d57 100644 --- a/packages/server/src/api/routes/tests/user.spec.js +++ b/packages/server/src/api/routes/tests/user.spec.js @@ -54,7 +54,8 @@ describe("/users", () => { method: "GET", url: `/api/users`, appId: appId, - permissionName: BUILTIN_PERMISSION_NAMES.WRITE, + permName1: BUILTIN_PERMISSION_NAMES.POWER, + permName2: BUILTIN_PERMISSION_NAMES.WRITE, }) }) @@ -81,7 +82,8 @@ describe("/users", () => { body: { name: "brandNewUser", username: "brandNewUser", password: "yeeooo", accessLevelId: BUILTIN_LEVELS.power._id }, url: `/api/users`, appId: appId, - permissionName: BUILTIN_PERMISSION_NAMES.WRITE, + permName1: BUILTIN_PERMISSION_NAMES.ADMIN, + permName2: BUILTIN_PERMISSION_NAMES.POWER, }) }) From be7febe49f6022ea0b8c52365fac2f7219116380 Mon Sep 17 00:00:00 2001 From: Michael Drury Date: Thu, 12 Nov 2020 17:43:17 +0000 Subject: [PATCH 05/15] Linting. --- packages/server/src/api/controllers/routing.js | 4 +--- packages/server/src/api/routes/tests/couchTestUtils.js | 4 +--- packages/server/src/middleware/authenticated.js | 4 +--- 3 files changed, 3 insertions(+), 9 deletions(-) diff --git a/packages/server/src/api/controllers/routing.js b/packages/server/src/api/controllers/routing.js index ae3f949833..ec2458a8ca 100644 --- a/packages/server/src/api/controllers/routing.js +++ b/packages/server/src/api/controllers/routing.js @@ -1,3 +1 @@ -exports.fetch = async ctx => { - -} \ No newline at end of file +exports.fetch = async ctx => {} diff --git a/packages/server/src/api/routes/tests/couchTestUtils.js b/packages/server/src/api/routes/tests/couchTestUtils.js index ec86c8b707..8df4382290 100644 --- a/packages/server/src/api/routes/tests/couchTestUtils.js +++ b/packages/server/src/api/routes/tests/couchTestUtils.js @@ -1,8 +1,6 @@ const CouchDB = require("../../../db") const supertest = require("supertest") -const { - BUILTIN_LEVELS, -} = require("../../../utilities/security/accessLevels") +const { BUILTIN_LEVELS } = require("../../../utilities/security/accessLevels") const { BUILTIN_PERMISSION_NAMES, } = require("../../../utilities/security/permissions") diff --git a/packages/server/src/middleware/authenticated.js b/packages/server/src/middleware/authenticated.js index c10f1387cf..e35592a85b 100644 --- a/packages/server/src/middleware/authenticated.js +++ b/packages/server/src/middleware/authenticated.js @@ -1,9 +1,7 @@ const jwt = require("jsonwebtoken") const STATUS_CODES = require("../utilities/statusCodes") const accessLevelController = require("../api/controllers/accesslevel") -const { - BUILTIN_LEVEL_IDS, -} = require("../utilities/security/accessLevels") +const { BUILTIN_LEVEL_IDS } = require("../utilities/security/accessLevels") const env = require("../environment") const { AuthTypes } = require("../constants") const { getAppId, getCookieName, setCookie } = require("../utilities") From a423664f4c1d28586a07dd2cd46da14d8eccb20d Mon Sep 17 00:00:00 2001 From: Michael Drury Date: Fri, 13 Nov 2020 15:35:20 +0000 Subject: [PATCH 06/15] Large update, tests passing, have simplifed access level API, access levels and permissions are now totally separate. --- .../store/screenTemplates/index.js | 2 +- .../modals/CreateTableModal.svelte | 4 +- .../userInterface/DetailScreenSelect.svelte | 4 +- .../EventsEditor/StateBindingCascader.svelte | 2 +- .../EventsEditor/actions/NavigateTo.svelte | 2 +- .../userInterface/NewScreenModal.svelte | 9 +- .../userInterface/ScreenSelect.svelte | 4 +- .../server/src/api/controllers/accesslevel.js | 73 ++----- .../server/src/api/controllers/application.js | 2 + packages/server/src/api/controllers/user.js | 6 +- packages/server/src/api/routes/accesslevel.js | 4 +- packages/server/src/api/routes/screen.js | 23 +- .../src/api/routes/tests/accesslevel.spec.js | 90 ++------ .../src/api/routes/tests/couchTestUtils.js | 12 +- .../server/src/api/routes/tests/user.spec.js | 6 +- .../src/automations/steps/createUser.js | 6 +- packages/server/src/constants/screens.js | 7 +- .../server/src/middleware/authenticated.js | 4 +- packages/server/src/middleware/authorized.js | 6 +- packages/server/src/routing/index.js | 2 + packages/server/src/routing/routingUtils.js | 1 + .../src/utilities/builder/setBuilderToken.js | 4 +- .../src/utilities/security/accessLevels.js | 68 +++++- packages/server/yarn.lock | 200 +----------------- 24 files changed, 152 insertions(+), 389 deletions(-) diff --git a/packages/builder/src/builderStore/store/screenTemplates/index.js b/packages/builder/src/builderStore/store/screenTemplates/index.js index 5abe428966..ddf48cbe44 100644 --- a/packages/builder/src/builderStore/store/screenTemplates/index.js +++ b/packages/builder/src/builderStore/store/screenTemplates/index.js @@ -24,7 +24,7 @@ const createTemplateOverride = (frontendState, create) => () => { } screen.props._id = uuid() screen.name = screen.props._id - screen.route = screen.route.toLowerCase() + screen.routing.route = screen.routing.route.toLowerCase() return screen } diff --git a/packages/builder/src/components/backend/TableNavigator/modals/CreateTableModal.svelte b/packages/builder/src/components/backend/TableNavigator/modals/CreateTableModal.svelte index 39d5f92a92..f73810c340 100644 --- a/packages/builder/src/components/backend/TableNavigator/modals/CreateTableModal.svelte +++ b/packages/builder/src/components/backend/TableNavigator/modals/CreateTableModal.svelte @@ -59,10 +59,10 @@ } // Create autolink to newly created list page - const listPage = screens.find(screen => + const listScreen = screens.find(screen => screen.props._instanceName.endsWith("List") ) - await store.actions.components.links.save(listPage.route, table.name) + await store.actions.components.links.save(listScreen.routing.route, table.name) // Navigate to new table $goto(`./table/${table._id}`) diff --git a/packages/builder/src/components/userInterface/DetailScreenSelect.svelte b/packages/builder/src/components/userInterface/DetailScreenSelect.svelte index e0b2813c0d..c119985196 100644 --- a/packages/builder/src/components/userInterface/DetailScreenSelect.svelte +++ b/packages/builder/src/components/userInterface/DetailScreenSelect.svelte @@ -17,11 +17,11 @@ .filter( screen => screen.props._component.endsWith("/rowdetail") || - screen.route.endsWith(":id") + screen.routing.route.endsWith(":id") ) .map(screen => ({ name: screen.props._instanceName, - url: screen.route, + url: screen.routing.route, sort: screen.props._component, })), ] diff --git a/packages/builder/src/components/userInterface/EventsEditor/StateBindingCascader.svelte b/packages/builder/src/components/userInterface/EventsEditor/StateBindingCascader.svelte index 948ab37efd..3de945adc5 100644 --- a/packages/builder/src/components/userInterface/EventsEditor/StateBindingCascader.svelte +++ b/packages/builder/src/components/userInterface/EventsEditor/StateBindingCascader.svelte @@ -25,7 +25,7 @@ + {/each} {:else} diff --git a/packages/builder/src/components/userInterface/EventsEditor/actions/NavigateTo.svelte b/packages/builder/src/components/userInterface/EventsEditor/actions/NavigateTo.svelte index 916f85dd0b..041237266e 100644 --- a/packages/builder/src/components/userInterface/EventsEditor/actions/NavigateTo.svelte +++ b/packages/builder/src/components/userInterface/EventsEditor/actions/NavigateTo.svelte @@ -10,7 +10,7 @@ + {/each} diff --git a/packages/builder/src/components/userInterface/NewScreenModal.svelte b/packages/builder/src/components/userInterface/NewScreenModal.svelte index b194ab9839..ebee601838 100644 --- a/packages/builder/src/components/userInterface/NewScreenModal.svelte +++ b/packages/builder/src/components/userInterface/NewScreenModal.svelte @@ -49,8 +49,8 @@ baseComponent = draftScreen.props._component } - if (draftScreen.route) { - route = draftScreen.route + if (draftScreen.routing) { + route = draftScreen.routing.route } } @@ -69,7 +69,8 @@ draftScreen.props._instanceName = name draftScreen.props._component = baseComponent - draftScreen.route = route + // TODO: need to fix this up correctly + draftScreen.routing = { route, accessLevelId: "ADMIN" } await store.actions.screens.create(draftScreen) if (createLink) { @@ -88,7 +89,7 @@ const routeNameExists = route => { return $allScreens.some( - screen => screen.route.toLowerCase() === route.toLowerCase() + screen => screen.routing.route.toLowerCase() === route.toLowerCase() ) } diff --git a/packages/builder/src/components/userInterface/ScreenSelect.svelte b/packages/builder/src/components/userInterface/ScreenSelect.svelte index b38116e4a1..1c7827e2dd 100644 --- a/packages/builder/src/components/userInterface/ScreenSelect.svelte +++ b/packages/builder/src/components/userInterface/ScreenSelect.svelte @@ -21,7 +21,7 @@ .filter(screen => !screen.props._component.endsWith("/rowdetail")) .map(screen => ({ name: screen.props._instanceName, - url: screen.route, + url: screen.routing.route, sort: screen.props._component, })), ] @@ -54,7 +54,7 @@ if (idBinding) { urls.push({ name: detailScreen.props._instanceName, - url: detailScreen.route.replace( + url: detailScreen.routing.route.replace( ":id", `{{ ${idBinding.runtimeBinding} }}` ), diff --git a/packages/server/src/api/controllers/accesslevel.js b/packages/server/src/api/controllers/accesslevel.js index 1a19309333..22bd1467fb 100644 --- a/packages/server/src/api/controllers/accesslevel.js +++ b/packages/server/src/api/controllers/accesslevel.js @@ -1,8 +1,8 @@ const CouchDB = require("../../db") -const { BUILTIN_LEVELS } = require("../../utilities/security/accessLevels") const { - BUILTIN_PERMISSION_NAMES, -} = require("../../utilities/security/permissions") + BUILTIN_LEVELS, + AccessLevel, +} = require("../../utilities/security/accessLevels") const { generateAccessLevelID, getAccessLevelParams, @@ -17,17 +17,7 @@ exports.fetch = async function(ctx) { ) const customAccessLevels = body.rows.map(row => row.doc) - const staticAccessLevels = [ - { - ...BUILTIN_LEVELS.admin, - permissions: [BUILTIN_PERMISSION_NAMES.ADMIN], - }, - { - ...BUILTIN_LEVELS.power, - permissions: [BUILTIN_PERMISSION_NAMES.POWER], - }, - ] - + const staticAccessLevels = [BUILTIN_LEVELS.ADMIN, BUILTIN_LEVELS.POWER] ctx.body = [...staticAccessLevels, ...customAccessLevels] } @@ -36,55 +26,18 @@ exports.find = async function(ctx) { ctx.body = await db.get(ctx.params.levelId) } -exports.update = async function(ctx) { - const db = new CouchDB(ctx.user.appId) - const level = await db.get(ctx.params.levelId) - level.name = ctx.body.name - level.permissions = ctx.request.body.permissions - const result = await db.put(level) - level._rev = result.rev - ctx.body = level - ctx.message = `Level ${level.name} updated successfully.` -} - -exports.patch = async function(ctx) { - const db = new CouchDB(ctx.user.appId) - const level = await db.get(ctx.params.levelId) - const { removedPermissions, addedPermissions, _rev } = ctx.request.body - - if (!_rev) throw new Error("Must supply a _rev to update an access level") - - level._rev = _rev - - if (removedPermissions) { - level.permissions = level.permissions.filter( - permission => removedPermissions.indexOf(permission) === -1 - ) - } - - if (addedPermissions) { - level.permissions = [ - ...new Set([...addedPermissions, ...level.permissions]), - ] - } - - const result = await db.put(level) - level._rev = result.rev - ctx.body = level - ctx.message = `Access Level ${level.name} updated successfully.` -} - -exports.create = async function(ctx) { +exports.save = async function(ctx) { const db = new CouchDB(ctx.user.appId) - const level = { - name: ctx.request.body.name, - _rev: ctx.request.body._rev, - permissions: ctx.request.body.permissions || [], - _id: generateAccessLevelID(), - type: "accesslevel", + let id = ctx.request.body._id || generateAccessLevelID() + const level = new AccessLevel( + id, + ctx.request.body.name, + ctx.request.body.inherits + ) + if (ctx.request.body._rev) { + level._rev = ctx.request.body._rev } - const result = await db.put(level) level._rev = result.rev ctx.body = level diff --git a/packages/server/src/api/controllers/application.js b/packages/server/src/api/controllers/application.js index 2185293352..59fb10a5b6 100644 --- a/packages/server/src/api/controllers/application.js +++ b/packages/server/src/api/controllers/application.js @@ -8,6 +8,7 @@ const fs = require("fs-extra") const { join, resolve } = require("../../utilities/centralPath") const packageJson = require("../../../package.json") const { createLinkView } = require("../../db/linkedRows") +const { createRoutingView } = require("../../routing") const { downloadTemplate } = require("../../utilities/templates") const { generateAppID, @@ -38,6 +39,7 @@ async function createInstance(template) { }) // add view for linked rows await createLinkView(appId) + await createRoutingView(appId) // replicate the template data to the instance DB if (template) { diff --git a/packages/server/src/api/controllers/user.js b/packages/server/src/api/controllers/user.js index 08044d4754..c51e1fd2b8 100644 --- a/packages/server/src/api/controllers/user.js +++ b/packages/server/src/api/controllers/user.js @@ -1,7 +1,9 @@ const CouchDB = require("../../db") const bcrypt = require("../../utilities/bcrypt") const { generateUserID, getUserParams } = require("../../db/utils") -const { BUILTIN_LEVEL_IDS } = require("../../utilities/security/accessLevels") +const { + BUILTIN_LEVEL_ID_ARRAY, +} = require("../../utilities/security/accessLevels") const { BUILTIN_PERMISSION_NAMES, } = require("../../utilities/security/permissions") @@ -96,7 +98,7 @@ exports.find = async function(ctx) { const checkAccessLevel = async (db, accessLevelId) => { if (!accessLevelId) return - if (BUILTIN_LEVEL_IDS.indexOf(accessLevelId) !== -1) { + if (BUILTIN_LEVEL_ID_ARRAY.indexOf(accessLevelId) !== -1) { return { _id: accessLevelId, name: accessLevelId, diff --git a/packages/server/src/api/routes/accesslevel.js b/packages/server/src/api/routes/accesslevel.js index f8bb70da63..1f21a1ea29 100644 --- a/packages/server/src/api/routes/accesslevel.js +++ b/packages/server/src/api/routes/accesslevel.js @@ -6,8 +6,7 @@ const { BUILDER } = require("../../utilities/security/permissions") const router = Router() router - .post("/api/accesslevels", authorized(BUILDER), controller.create) - .put("/api/accesslevels", authorized(BUILDER), controller.update) + .post("/api/accesslevels", authorized(BUILDER), controller.save) .get("/api/accesslevels", authorized(BUILDER), controller.fetch) .get("/api/accesslevels/:levelId", authorized(BUILDER), controller.find) .delete( @@ -15,6 +14,5 @@ router authorized(BUILDER), controller.destroy ) - .patch("/api/accesslevels/:levelId", authorized(BUILDER), controller.patch) module.exports = router diff --git a/packages/server/src/api/routes/screen.js b/packages/server/src/api/routes/screen.js index 1804bec27e..9bfd7a3973 100644 --- a/packages/server/src/api/routes/screen.js +++ b/packages/server/src/api/routes/screen.js @@ -12,17 +12,20 @@ function generateSaveValidation() { return joiValidator.body(Joi.object({ _css: Joi.string().allow(""), name: Joi.string().required(), - route: Joi.string().required(), + routing: Joi.array().items(Joi.object({ + route: Joi.string().required(), + accessLevelId: Joi.string().required(), + })).required(), 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(), - }).required().unknown(true), - }).unknown(true)) + _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(), + }).required().unknown(true), + }).unknown(true)) } router diff --git a/packages/server/src/api/routes/tests/accesslevel.spec.js b/packages/server/src/api/routes/tests/accesslevel.spec.js index 01ca97664a..eeb786f7d3 100644 --- a/packages/server/src/api/routes/tests/accesslevel.spec.js +++ b/packages/server/src/api/routes/tests/accesslevel.spec.js @@ -6,9 +6,10 @@ const { defaultHeaders } = require("./couchTestUtils") const { - BUILTIN_LEVELS, + BUILTIN_LEVEL_IDS, } = require("../../../utilities/security/accessLevels") -const { BUILTIN_PERMISSION_NAMES } = require("../../../utilities/security/permissions") + +const accessLevelBody = { name: "user", inherits: BUILTIN_LEVEL_IDS.BASIC } describe("/accesslevels", () => { let server @@ -37,7 +38,7 @@ describe("/accesslevels", () => { it("returns a success message when level is successfully created", async () => { const res = await request .post(`/api/accesslevels`) - .send({ name: "user" }) + .send(accessLevelBody) .set(defaultHeaders(appId)) .expect('Content-Type', /json/) .expect(200) @@ -45,7 +46,6 @@ describe("/accesslevels", () => { expect(res.res.statusMessage).toEqual("Access Level 'user' created successfully.") expect(res.body._id).toBeDefined() expect(res.body._rev).toBeDefined() - expect(res.body.permissions).toEqual([]) }) }); @@ -55,7 +55,7 @@ describe("/accesslevels", () => { it("should list custom levels, plus 2 default levels", async () => { const createRes = await request .post(`/api/accesslevels`) - .send({ name: "user", permissions: [BUILTIN_PERMISSION_NAMES.READ_ONLY] }) + .send(accessLevelBody) .set(defaultHeaders(appId)) .expect('Content-Type', /json/) .expect(200) @@ -70,16 +70,17 @@ describe("/accesslevels", () => { expect(res.body.length).toBe(3) - const adminLevel = res.body.find(r => r._id === BUILTIN_LEVELS.admin._id) + const adminLevel = res.body.find(r => r._id === BUILTIN_LEVEL_IDS.ADMIN) + expect(adminLevel.inherits).toEqual(BUILTIN_LEVEL_IDS.POWER) expect(adminLevel).toBeDefined() - expect(adminLevel.permissions).toEqual([BUILTIN_PERMISSION_NAMES.ADMIN]) - const powerUserLevel = res.body.find(r => r._id === BUILTIN_LEVELS.power._id) + const powerUserLevel = res.body.find(r => r._id === BUILTIN_LEVEL_IDS.POWER) + expect(powerUserLevel.inherits).toEqual(BUILTIN_LEVEL_IDS.BASIC) expect(powerUserLevel).toBeDefined() - expect(powerUserLevel.permissions).toEqual([BUILTIN_PERMISSION_NAMES.POWER]) const customLevelFetched = res.body.find(r => r._id === customLevel._id) - expect(customLevelFetched.permissions).toEqual([BUILTIN_PERMISSION_NAMES.READ_ONLY]) + expect(customLevelFetched.inherits).toEqual(BUILTIN_LEVEL_IDS.BASIC) + expect(customLevelFetched).toBeDefined() }) }); @@ -88,7 +89,7 @@ describe("/accesslevels", () => { it("should delete custom access level", async () => { const createRes = await request .post(`/api/accesslevels`) - .send({ name: "user", permissions: [BUILTIN_PERMISSION_NAMES.READ_ONLY] }) + .send({ name: "user" }) .set(defaultHeaders(appId)) .expect('Content-Type', /json/) .expect(200) @@ -106,71 +107,4 @@ describe("/accesslevels", () => { .expect(404) }) }) - - describe("patch", () => { - it("should add given permissions", async () => { - const createRes = await request - .post(`/api/accesslevels`) - .send({ name: "user", permissions: [BUILTIN_PERMISSION_NAMES.READ_ONLY] }) - .set(defaultHeaders(appId)) - .expect('Content-Type', /json/) - .expect(200) - - const customLevel = createRes.body - - await request - .patch(`/api/accesslevels/${customLevel._id}`) - .send({ - _rev: customLevel._rev, - addedPermissions: [ BUILTIN_PERMISSION_NAMES.WRITE ] - }) - .set(defaultHeaders(appId)) - .expect('Content-Type', /json/) - .expect(200) - - const finalRes = await request - .get(`/api/accesslevels/${customLevel._id}`) - .set(defaultHeaders(appId)) - .expect(200) - - expect(finalRes.body.permissions.length).toBe(2) - expect(finalRes.body.permissions.indexOf(BUILTIN_PERMISSION_NAMES.WRITE)).not.toBe(-1) - expect(finalRes.body.permissions.indexOf(BUILTIN_PERMISSION_NAMES.READ_ONLY)).not.toBe(-1) - }) - - it("should remove given permissions", async () => { - const createRes = await request - .post(`/api/accesslevels`) - .send({ - name: "user", - permissions: [ - BUILTIN_PERMISSION_NAMES.READ_ONLY, - BUILTIN_PERMISSION_NAMES.WRITE, - ] - }) - .set(defaultHeaders(appId)) - .expect('Content-Type', /json/) - .expect(200) - - const customLevel = createRes.body - - await request - .patch(`/api/accesslevels/${customLevel._id}`) - .send({ - _rev: customLevel._rev, - removedPermissions: [BUILTIN_PERMISSION_NAMES.WRITE] - }) - .set(defaultHeaders(appId)) - .expect('Content-Type', /json/) - .expect(200) - - const finalRes = await request - .get(`/api/accesslevels/${customLevel._id}`) - .set(defaultHeaders(appId)) - .expect(200) - - expect(finalRes.body.permissions.length).toBe(1) - expect(finalRes.body.permissions.indexOf(BUILTIN_PERMISSION_NAMES.READ_ONLY)).not.toBe(-1) - }) - }) }); diff --git a/packages/server/src/api/routes/tests/couchTestUtils.js b/packages/server/src/api/routes/tests/couchTestUtils.js index 8df4382290..0e137f1a44 100644 --- a/packages/server/src/api/routes/tests/couchTestUtils.js +++ b/packages/server/src/api/routes/tests/couchTestUtils.js @@ -1,6 +1,8 @@ const CouchDB = require("../../../db") const supertest = require("supertest") -const { BUILTIN_LEVELS } = require("../../../utilities/security/accessLevels") +const { + BUILTIN_LEVEL_IDS, +} = require("../../../utilities/security/accessLevels") const { BUILTIN_PERMISSION_NAMES, } = require("../../../utilities/security/permissions") @@ -24,7 +26,7 @@ exports.supertest = async () => { exports.defaultHeaders = appId => { const builderUser = { userId: "BUILDER", - accessLevelId: BUILTIN_LEVELS.builder._id, + accessLevelId: BUILTIN_LEVEL_IDS.BUILDER, } const builderToken = jwt.sign(builderUser, env.JWT_SECRET) @@ -124,7 +126,7 @@ exports.createUser = async ( name: "Bill", username, password, - accessLevelId: BUILTIN_LEVELS.power._id, + accessLevelId: BUILTIN_LEVEL_IDS.POWER, }) return res.body } @@ -180,13 +182,13 @@ const createUserWithPermissions = async ( name: username, username, password, - accessLevelId: BUILTIN_LEVELS.power._id, + accessLevelId: BUILTIN_LEVEL_IDS.POWER, permissions, }) const anonUser = { userId: "ANON", - accessLevelId: BUILTIN_LEVELS.anon._id, + accessLevelId: BUILTIN_LEVEL_IDS.ANON, appId: appId, version: packageJson.version, } diff --git a/packages/server/src/api/routes/tests/user.spec.js b/packages/server/src/api/routes/tests/user.spec.js index 2862903d57..f9277039ea 100644 --- a/packages/server/src/api/routes/tests/user.spec.js +++ b/packages/server/src/api/routes/tests/user.spec.js @@ -9,7 +9,7 @@ const { BUILTIN_PERMISSION_NAMES, } = require("../../../utilities/security/permissions") const { - BUILTIN_LEVELS + BUILTIN_LEVEL_IDS, } = require("../../../utilities/security/accessLevels") describe("/users", () => { @@ -67,7 +67,7 @@ describe("/users", () => { const res = await request .post(`/api/users`) .set(defaultHeaders(appId)) - .send({ name: "Bill", username: "bill", password: "bills_password", accessLevelId: BUILTIN_LEVELS.power._id }) + .send({ name: "Bill", username: "bill", password: "bills_password", accessLevelId: BUILTIN_LEVEL_IDS.POWER }) .expect(200) .expect('Content-Type', /json/) @@ -79,7 +79,7 @@ describe("/users", () => { await testPermissionsForEndpoint({ request, method: "POST", - body: { name: "brandNewUser", username: "brandNewUser", password: "yeeooo", accessLevelId: BUILTIN_LEVELS.power._id }, + body: { name: "brandNewUser", username: "brandNewUser", password: "yeeooo", accessLevelId: BUILTIN_LEVEL_IDS.POWER }, url: `/api/users`, appId: appId, permName1: BUILTIN_PERMISSION_NAMES.ADMIN, diff --git a/packages/server/src/automations/steps/createUser.js b/packages/server/src/automations/steps/createUser.js index 40f7d76a38..4b6250ce36 100644 --- a/packages/server/src/automations/steps/createUser.js +++ b/packages/server/src/automations/steps/createUser.js @@ -11,7 +11,7 @@ module.exports.definition = { type: "ACTION", stepId: "CREATE_USER", inputs: { - accessLevelId: accessLevels.BUILTIN_LEVELS.power._id, + accessLevelId: accessLevels.BUILTIN_LEVEL_IDS.POWER, }, schema: { inputs: { @@ -28,8 +28,8 @@ module.exports.definition = { accessLevelId: { type: "string", title: "Access Level", - enum: accessLevels.BUILTIN_LEVELS, - pretty: accessLevels.BUILTIN_LEVEL_NAMES, + enum: accessLevels.BUILTIN_LEVEL_IDS, + pretty: accessLevels.BUILTIN_LEVEL_NAME_ARRAY, }, }, required: ["username", "password", "accessLevelId"], diff --git a/packages/server/src/constants/screens.js b/packages/server/src/constants/screens.js index f9a0fb68dc..5c5a9dfd26 100644 --- a/packages/server/src/constants/screens.js +++ b/packages/server/src/constants/screens.js @@ -1,3 +1,5 @@ +const { BUILTIN_LEVEL_IDS } = require("../utilities/security/accessLevels") + exports.HOME_SCREEN = { description: "", url: "", @@ -98,6 +100,9 @@ exports.HOME_SCREEN = { ], _instanceName: "Home", }, - route: "/", + routing: { + route: "/", + accessLevelId: BUILTIN_LEVEL_IDS.BASIC, + }, name: "d834fea2-1b3e-4320-ab34-f9009f5ecc59", } diff --git a/packages/server/src/middleware/authenticated.js b/packages/server/src/middleware/authenticated.js index e35592a85b..c0fbbdb86c 100644 --- a/packages/server/src/middleware/authenticated.js +++ b/packages/server/src/middleware/authenticated.js @@ -1,7 +1,7 @@ const jwt = require("jsonwebtoken") const STATUS_CODES = require("../utilities/statusCodes") const accessLevelController = require("../api/controllers/accesslevel") -const { BUILTIN_LEVEL_IDS } = require("../utilities/security/accessLevels") +const { BUILTIN_LEVEL_ID_ARRAY } = require("../utilities/security/accessLevels") const env = require("../environment") const { AuthTypes } = require("../constants") const { getAppId, getCookieName, setCookie } = require("../utilities") @@ -69,7 +69,7 @@ module.exports = async (ctx, next) => { * @param {*} accessLevelId - the id of the users access level */ const getAccessLevel = async (appId, accessLevelId) => { - if (BUILTIN_LEVEL_IDS.indexOf(accessLevelId) !== -1) { + if (BUILTIN_LEVEL_ID_ARRAY.indexOf(accessLevelId) !== -1) { return { _id: accessLevelId, name: accessLevelId, diff --git a/packages/server/src/middleware/authorized.js b/packages/server/src/middleware/authorized.js index 93ad57d071..5b36448463 100644 --- a/packages/server/src/middleware/authorized.js +++ b/packages/server/src/middleware/authorized.js @@ -1,4 +1,4 @@ -const { BUILTIN_LEVELS } = require("../utilities/security/accessLevels") +const { BUILTIN_LEVEL_IDS } = require("../utilities/security/accessLevels") const { PermissionTypes, doesHavePermission, @@ -7,7 +7,7 @@ const env = require("../environment") const { apiKeyTable } = require("../db/dynamoClient") const { AuthTypes } = require("../constants") -const ADMIN_PERMS = [BUILTIN_LEVELS.admin._id, BUILTIN_LEVELS.builder._id] +const ADMIN_ACCESS = [BUILTIN_LEVEL_IDS.ADMIN, BUILTIN_LEVEL_IDS.BUILDER] const LOCAL_PASS = new RegExp(["webhooks/trigger", "webhooks/schema"].join("|")) @@ -49,7 +49,7 @@ module.exports = (permType, permLevel = null) => async (ctx, next) => { const accessLevel = ctx.user.accessLevel const permissions = ctx.user.permissions - if (ADMIN_PERMS.indexOf(accessLevel._id) !== -1) { + if (ADMIN_ACCESS.indexOf(accessLevel._id) !== -1) { return next() } diff --git a/packages/server/src/routing/index.js b/packages/server/src/routing/index.js index 5de85861b4..5548f45723 100644 --- a/packages/server/src/routing/index.js +++ b/packages/server/src/routing/index.js @@ -17,3 +17,5 @@ exports.getRoutingInfo = async appId => { } } } + +exports.createRoutingView = createRoutingView diff --git a/packages/server/src/routing/routingUtils.js b/packages/server/src/routing/routingUtils.js index 7a51079b8a..e5a610e70d 100644 --- a/packages/server/src/routing/routingUtils.js +++ b/packages/server/src/routing/routingUtils.js @@ -9,6 +9,7 @@ exports.createRoutingView = async appId => { map: function(doc) { if (doc._id.startsWith(SCREEN_PREFIX)) { emit(doc._id, { + id: doc._id, routing: doc.routing, }) } diff --git a/packages/server/src/utilities/builder/setBuilderToken.js b/packages/server/src/utilities/builder/setBuilderToken.js index 982fe9ad09..f3adf079ad 100644 --- a/packages/server/src/utilities/builder/setBuilderToken.js +++ b/packages/server/src/utilities/builder/setBuilderToken.js @@ -1,4 +1,4 @@ -const { BUILTIN_LEVELS } = require("../security/accessLevels") +const { BUILTIN_LEVEL_IDS } = require("../security/accessLevels") const { BUILTIN_PERMISSION_NAMES } = require("../security/permissions") const env = require("../../environment") const CouchDB = require("../../db") @@ -10,7 +10,7 @@ const APP_PREFIX = DocumentTypes.APP + SEPARATOR module.exports = async (ctx, appId, version) => { const builderUser = { userId: "BUILDER", - accessLevelId: BUILTIN_LEVELS.builder._id, + accessLevelId: BUILTIN_LEVEL_IDS.BUILDER, permissions: [BUILTIN_PERMISSION_NAMES.ADMIN], version, } diff --git a/packages/server/src/utilities/security/accessLevels.js b/packages/server/src/utilities/security/accessLevels.js index 48e088afb5..a77d9bc6aa 100644 --- a/packages/server/src/utilities/security/accessLevels.js +++ b/packages/server/src/utilities/security/accessLevels.js @@ -1,14 +1,68 @@ -exports.BUILTIN_LEVELS = { - admin: { _id: "ADMIN", name: "Admin" }, - power: { _id: "POWER_USER", name: "Power user" }, - builder: { _id: "BUILDER", name: "Builder" }, - anon: { _id: "ANON", name: "Anonymous" }, +const CouchDB = require("../../db") + +const BUILTIN_IDS = { + ADMIN: "ADMIN", + POWER: "POWER_USER", + BASIC: "BASIC", + ANON: "ANON", + BUILDER: "BUILDER", } -exports.BUILTIN_LEVEL_IDS = Object.values(exports.BUILTIN_LEVELS).map( +function AccessLevel(id, name, inherits = null) { + this._id = id + this.name = name + if (inherits) { + this.inherits = inherits + } +} + +exports.BUILTIN_LEVELS = { + ADMIN: new AccessLevel(BUILTIN_IDS.ADMIN, "Admin", BUILTIN_IDS.POWER), + POWER: new AccessLevel(BUILTIN_IDS.POWER, "Admin", BUILTIN_IDS.BASIC), + BASIC: new AccessLevel(BUILTIN_IDS.BASIC, "Basic", BUILTIN_IDS.ANON), + ANON: new AccessLevel(BUILTIN_IDS.ANON, "Anonymous"), + BUILDER: new AccessLevel(BUILTIN_IDS.BUILDER, "Builder"), +} + +exports.BUILTIN_LEVEL_ID_ARRAY = Object.values(exports.BUILTIN_LEVELS).map( level => level._id ) -exports.BUILTIN_LEVEL_NAMES = Object.values(exports.BUILTIN_LEVELS).map( +exports.BUILTIN_LEVEL_NAME_ARRAY = Object.values(exports.BUILTIN_LEVELS).map( level => level.name ) + +function isBuiltin(accessLevel) { + return BUILTIN_IDS.indexOf(accessLevel) !== -1 +} + +exports.getAccessLevel = async (appId, accessLevelId) => { + if (isBuiltin(accessLevelId)) { + return Object.values(exports.BUILTIN_LEVELS).find( + level => level._id === accessLevelId + ) + } + const db = new CouchDB(appId) + return await db.get(accessLevelId) +} + +exports.hasAccess = async (appId, tryingAccessLevelId, userAccessLevelId) => { + // special first case, if they are equal then access is allowed, no need to try anything + if (tryingAccessLevelId === userAccessLevelId) { + return true + } + let userAccess = await exports.getAccessLevel(appId, userAccessLevelId) + // check if inherited makes it possible + while (userAccess.inherits) { + if (tryingAccessLevelId === userAccess.inherits) { + return true + } + // go to get the inherited incase it inherits anything + userAccess = await exports.getAccessLevel(appId, userAccess.inherits) + } + return false +} + +exports.BUILTIN_LEVEL_IDS = BUILTIN_IDS +exports.isBuiltin = isBuiltin +exports.AccessLevel = AccessLevel diff --git a/packages/server/yarn.lock b/packages/server/yarn.lock index 9e6b472dac..8bd53587e1 100644 --- a/packages/server/yarn.lock +++ b/packages/server/yarn.lock @@ -200,15 +200,6 @@ lodash "^4.17.19" to-fast-properties "^2.0.0" -"@budibase/client@^0.3.6": - version "0.3.7" - resolved "https://registry.yarnpkg.com/@budibase/client/-/client-0.3.7.tgz#8ed2d40d91ba3788a69ee5db5078f757adb4187f" - integrity sha512-EgpHfw/WOUYeCG4cILDbaN2WFBDSPS698Z+So7FP5l+4E1fvmqtpXVKJYsviwYEx8AKKYyU3nuDi0l6xzb5Flg== - dependencies: - deep-equal "^2.0.1" - mustache "^4.0.1" - regexparam "^1.3.0" - "@cnakazawa/watch@^1.0.3": version "1.0.4" resolved "https://registry.yarnpkg.com/@cnakazawa/watch/-/watch-1.0.4.tgz#f864ae85004d0fcab6f50be9141c4da368d1656a" @@ -1236,11 +1227,6 @@ array-equal@^1.0.0: resolved "https://registry.yarnpkg.com/array-equal/-/array-equal-1.0.0.tgz#8c2a5ef2472fd9ea742b04c77a75093ba2757c93" integrity sha1-jCpe8kcv2ep0KwTHenUJO6J1fJM= -array-filter@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/array-filter/-/array-filter-1.0.0.tgz#baf79e62e6ef4c2a4c0b831232daffec251f9d83" - integrity sha1-uveeYubvTCpMC4MSMtr/7CUfnYM= - array-unique@^0.3.2: version "0.3.2" resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428" @@ -1313,13 +1299,6 @@ atomic-sleep@^1.0.0: resolved "https://registry.yarnpkg.com/atomic-sleep/-/atomic-sleep-1.0.0.tgz#eb85b77a601fc932cfe432c5acd364a9e2c9075b" integrity sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ== -available-typed-arrays@^1.0.0, available-typed-arrays@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.2.tgz#6b098ca9d8039079ee3f77f7b783c4480ba513f5" - integrity sha512-XWX3OX8Onv97LMk/ftVyBibpGwY5a8SmuxZPzeOxqmuEqUCOM9ZE+uIaD1VNJ5QnvU2UQusvmKbuM1FR8QWGfQ== - dependencies: - array-filter "^1.0.0" - aws-sdk@^2.767.0: version "2.771.0" resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.771.0.tgz#ff4beb0a04d6ab1ae962c85dfb42e3e9bfe2b93b" @@ -1687,14 +1666,6 @@ cacheable-request@^6.0.0: normalize-url "^4.1.0" responselike "^1.0.2" -call-bind@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.0.tgz#24127054bb3f9bdcb4b1fb82418186072f77b8ce" - integrity sha512-AEXsYIyyDY3MCzbwdhzG3Jx1R0J2wetQyUynn6dYHAO+bg8l1k7jwZtRv4ryryFs7EP+NDlikJlVe59jr0cM2w== - dependencies: - function-bind "^1.1.1" - get-intrinsic "^1.0.0" - callsites@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" @@ -2193,26 +2164,6 @@ decompress@^4.2.1: pify "^2.3.0" strip-dirs "^2.0.0" -deep-equal@^2.0.1: - version "2.0.4" - resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-2.0.4.tgz#6b0b407a074666033169df3acaf128e1c6f3eab6" - integrity sha512-BUfaXrVoCfgkOQY/b09QdO9L3XNoF2XH0A3aY9IQwQL/ZjLOe8FQgCNVl1wiolhsFo8kFdO9zdPViCPbmaJA5w== - dependencies: - es-abstract "^1.18.0-next.1" - es-get-iterator "^1.1.0" - is-arguments "^1.0.4" - is-date-object "^1.0.2" - is-regex "^1.1.1" - isarray "^2.0.5" - object-is "^1.1.3" - object-keys "^1.1.1" - object.assign "^4.1.1" - regexp.prototype.flags "^1.3.0" - side-channel "^1.0.3" - which-boxed-primitive "^1.0.1" - which-collection "^1.0.1" - which-typed-array "^1.1.2" - deep-equal@~1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.0.1.tgz#f5d260292b660e084eff4cdbc9f08ad3247448b5" @@ -2594,7 +2545,7 @@ error-ex@^1.3.1: dependencies: is-arrayish "^0.2.1" -es-abstract@^1.17.0-next.1, es-abstract@^1.17.2, es-abstract@^1.17.4, es-abstract@^1.17.5: +es-abstract@^1.17.0-next.1, es-abstract@^1.17.2, es-abstract@^1.17.5: version "1.17.7" resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.17.7.tgz#a4de61b2f66989fc7421676c1cb9787573ace54c" integrity sha512-VBl/gnfcJ7OercKA9MVaegWsBHFjV492syMudcnQZvt/Dw8ezpcOHYZXa/J96O8vx+g4x65YKhxOwDUh63aS5g== @@ -2611,7 +2562,7 @@ es-abstract@^1.17.0-next.1, es-abstract@^1.17.2, es-abstract@^1.17.4, es-abstrac string.prototype.trimend "^1.0.1" string.prototype.trimstart "^1.0.1" -es-abstract@^1.18.0-next.0, es-abstract@^1.18.0-next.1: +es-abstract@^1.18.0-next.0: version "1.18.0-next.1" resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.18.0-next.1.tgz#6e3a0a4bda717e5023ab3b8e90bec36108d22c68" integrity sha512-I4UGspA0wpZXWENrdA0uHbnhte683t3qT/1VFH9aX2dA5PPSf6QW5HHXf5HImaqPmjXaVeVk4RGWnaylmV7uAA== @@ -2629,20 +2580,6 @@ es-abstract@^1.18.0-next.0, es-abstract@^1.18.0-next.1: string.prototype.trimend "^1.0.1" string.prototype.trimstart "^1.0.1" -es-get-iterator@^1.1.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/es-get-iterator/-/es-get-iterator-1.1.1.tgz#b93ddd867af16d5118e00881396533c1c6647ad9" - integrity sha512-qorBw8Y7B15DVLaJWy6WdEV/ZkieBcu6QCq/xzWzGOKJqgG1j754vXRfZ3NY7HSShneqU43mPB4OkQBTkvHhFw== - dependencies: - call-bind "^1.0.0" - get-intrinsic "^1.0.1" - has-symbols "^1.0.1" - is-arguments "^1.0.4" - is-map "^2.0.1" - is-set "^2.0.1" - is-string "^1.0.5" - isarray "^2.0.5" - es-to-primitive@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a" @@ -3315,15 +3252,6 @@ get-caller-file@^2.0.1: resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== -get-intrinsic@^1.0.0, get-intrinsic@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.0.1.tgz#94a9768fcbdd0595a1c9273aacf4c89d075631be" - integrity sha512-ZnWP+AmS1VUaLgTRy47+zKtjTxz+0xMpx3I52i+aalBK1QP19ggLF3Db89KJX7kjfOfP2eoa01qc++GwPgufPg== - dependencies: - function-bind "^1.1.1" - has "^1.0.3" - has-symbols "^1.0.1" - get-stream@3.0.0, get-stream@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14" @@ -3848,21 +3776,11 @@ is-accessor-descriptor@^1.0.0: dependencies: kind-of "^6.0.0" -is-arguments@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.0.4.tgz#3faf966c7cba0ff437fb31f6250082fcf0448cf3" - integrity sha512-xPh0Rmt8NE65sNzvyUmWgI1tz3mKq74lGA0mL8LYZcoIzKOzDh6HmrYm3d18k60nHerC8A9Km8kYu87zfSFnLA== - is-arrayish@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" integrity sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0= -is-bigint@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.0.tgz#73da8c33208d00f130e9b5e15d23eac9215601c4" - integrity sha512-t5mGUXC/xRheCK431ylNiSkGGpBp8bHENBcENTkDT6ppwPzEVxNGZRvgvmOEfbWkFhA7D2GEuE2mmQTr78sl2g== - is-binary-path@~2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" @@ -3870,11 +3788,6 @@ is-binary-path@~2.1.0: dependencies: binary-extensions "^2.0.0" -is-boolean-object@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.0.1.tgz#10edc0900dd127697a92f6f9807c7617d68ac48e" - integrity sha512-TqZuVwa/sppcrhUCAYkGBk7w0yxfQQnxq28fjkO53tnK9FQXmdwz2JS5+GjsWQ6RByES1K40nI+yDic5c9/aAQ== - is-buffer@^1.1.5: version "1.1.6" resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" @@ -3911,7 +3824,7 @@ is-data-descriptor@^1.0.0: dependencies: kind-of "^6.0.0" -is-date-object@^1.0.1, is-date-object@^1.0.2: +is-date-object@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.2.tgz#bda736f2cd8fd06d32844e7743bfa7494c3bfd7e" integrity sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g== @@ -3991,11 +3904,6 @@ is-installed-globally@^0.3.1: global-dirs "^2.0.1" is-path-inside "^3.0.1" -is-map@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/is-map/-/is-map-2.0.1.tgz#520dafc4307bb8ebc33b813de5ce7c9400d644a1" - integrity sha512-T/S49scO8plUiAOA2DBTBG3JHpn1yiw0kRp6dgiZ0v2/6twi5eiB0rHtHFH9ZIrvlWc6+4O+m4zg5+Z833aXgw== - is-natural-number@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/is-natural-number/-/is-natural-number-4.0.1.tgz#ab9d76e1db4ced51e35de0c72ebecf09f734cde8" @@ -4011,11 +3919,6 @@ is-npm@^4.0.0: resolved "https://registry.yarnpkg.com/is-npm/-/is-npm-4.0.0.tgz#c90dd8380696df87a7a6d823c20d0b12bbe3c84d" integrity sha512-96ECIfh9xtDDlPylNPXhzjsykHsMJZ18ASpaWzQyBr4YRTcVjUvzaHayDAES2oU/3KpljhHUjtSRNiDwi0F0ig== -is-number-object@^1.0.3: - version "1.0.4" - resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.4.tgz#36ac95e741cf18b283fc1ddf5e83da798e3ec197" - integrity sha512-zohwelOAur+5uXtk8O3GPQ1eAcu4ZX3UwxQhUlfFFMNpUd83gXgjbhJh6HmB6LUNV/ieOLQuDwJO3dWJosUeMw== - is-number@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195" @@ -4067,21 +3970,11 @@ is-retry-allowed@^1.1.0: resolved "https://registry.yarnpkg.com/is-retry-allowed/-/is-retry-allowed-1.2.0.tgz#d778488bd0a4666a3be8a1482b9f2baafedea8b4" integrity sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg== -is-set@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/is-set/-/is-set-2.0.1.tgz#d1604afdab1724986d30091575f54945da7e5f43" - integrity sha512-eJEzOtVyenDs1TMzSQ3kU3K+E0GUS9sno+F0OBT97xsgcJsF9nXMBtkT9/kut5JEpM7oL7X/0qxR17K3mcwIAA== - is-stream@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ= -is-string@^1.0.4, is-string@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.5.tgz#40493ed198ef3ff477b8c7f92f644ec82a5cd3a6" - integrity sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ== - is-symbol@^1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.3.tgz#38e1014b9e6329be0de9d24a414fd7441ec61937" @@ -4098,16 +3991,6 @@ is-type-of@^1.0.0: is-class-hotfix "~0.0.6" isstream "~0.1.2" -is-typed-array@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.3.tgz#a4ff5a5e672e1a55f99c7f54e59597af5c1df04d" - integrity sha512-BSYUBOK/HJibQ30wWkWold5txYwMUXQct9YHAQJr8fSwvZoiglcqB0pd7vEN23+Tsi9IUEjztdOSzl4qLVYGTQ== - dependencies: - available-typed-arrays "^1.0.0" - es-abstract "^1.17.4" - foreach "^2.0.5" - has-symbols "^1.0.1" - is-typedarray@^1.0.0, is-typedarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" @@ -4118,16 +4001,6 @@ is-utf8@^0.2.0: resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72" integrity sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI= -is-weakmap@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/is-weakmap/-/is-weakmap-2.0.1.tgz#5008b59bdc43b698201d18f62b37b2ca243e8cf2" - integrity sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA== - -is-weakset@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/is-weakset/-/is-weakset-2.0.1.tgz#e9a0af88dbd751589f5e50d80f4c98b780884f83" - integrity sha512-pi4vhbhVHGLxohUw7PhGsueT4vRGFoXhP7+RGN0jKIv9+8PWYCQTqtADngrxOm2g46hoH0+g8uZZBzMrvVGDmw== - is-windows@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" @@ -4153,11 +4026,6 @@ isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0: resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= -isarray@^2.0.5: - version "2.0.5" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723" - integrity sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw== - isbinaryfile@^4.0.6: version "4.0.6" resolved "https://registry.yarnpkg.com/isbinaryfile/-/isbinaryfile-4.0.6.tgz#edcb62b224e2b4710830b67498c8e4e5a4d2610b" @@ -5696,14 +5564,6 @@ object-inspect@^1.8.0: resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.8.0.tgz#df807e5ecf53a609cc6bfe93eac3cc7be5b3a9d0" integrity sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA== -object-is@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.1.3.tgz#2e3b9e65560137455ee3bd62aec4d90a2ea1cc81" - integrity sha512-teyqLvFWzLkq5B9ki8FVWA902UER2qkxmdA4nLf+wjOLAWgxzCWZNCxpDq9MvE8MmhWNr+I8w3BN49Vx36Y6Xg== - dependencies: - define-properties "^1.1.3" - es-abstract "^1.18.0-next.1" - object-keys@^1.0.12, object-keys@^1.0.6, object-keys@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" @@ -6573,19 +6433,6 @@ regex-not@^1.0.0, regex-not@^1.0.2: extend-shallow "^3.0.2" safe-regex "^1.1.0" -regexp.prototype.flags@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.3.0.tgz#7aba89b3c13a64509dabcf3ca8d9fbb9bdf5cb75" - integrity sha512-2+Q0C5g951OlYlJz6yu5/M33IcsESLlLfsyIaLJaG4FA2r4yP8MvVMJUUP/fVBkSpbbbZlS5gynbEWLipiiXiQ== - dependencies: - define-properties "^1.1.3" - es-abstract "^1.17.0-next.1" - -regexparam@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/regexparam/-/regexparam-1.3.0.tgz#2fe42c93e32a40eff6235d635e0ffa344b92965f" - integrity sha512-6IQpFBv6e5vz1QAqI+V4k8P2e/3gRrqfCJ9FI+O1FLQTO+Uz6RXZEZOPmTJ6hlGj7gkERzY5BRCv09whKP96/g== - regexpp@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-2.0.1.tgz#8d19d31cf632482b589049f8281f93dbcba4d07f" @@ -6945,14 +6792,6 @@ shellwords@^0.1.1: resolved "https://registry.yarnpkg.com/shellwords/-/shellwords-0.1.1.tgz#d6b9181c1a48d397324c84871efbcfc73fc0654b" integrity sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww== -side-channel@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.3.tgz#cdc46b057550bbab63706210838df5d4c19519c3" - integrity sha512-A6+ByhlLkksFoUepsGxfj5x1gTSrs+OydsRptUxeNCabQpCFUvcwIczgOigI8vhY/OJCnPnyE9rGiwgvr9cS1g== - dependencies: - es-abstract "^1.18.0-next.0" - object-inspect "^1.8.0" - signal-exit@^3.0.0, signal-exit@^3.0.2: version "3.0.3" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c" @@ -7980,44 +7819,11 @@ whatwg-url@^7.0.0: tr46 "^1.0.1" webidl-conversions "^4.0.2" -which-boxed-primitive@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.1.tgz#cbe8f838ebe91ba2471bb69e9edbda67ab5a5ec1" - integrity sha512-7BT4TwISdDGBgaemWU0N0OU7FeAEJ9Oo2P1PHRm/FCWoEi2VLWC9b6xvxAA3C/NMpxg3HXVgi0sMmGbNUbNepQ== - dependencies: - is-bigint "^1.0.0" - is-boolean-object "^1.0.0" - is-number-object "^1.0.3" - is-string "^1.0.4" - is-symbol "^1.0.2" - -which-collection@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/which-collection/-/which-collection-1.0.1.tgz#70eab71ebbbd2aefaf32f917082fc62cdcb70906" - integrity sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A== - dependencies: - is-map "^2.0.1" - is-set "^2.0.1" - is-weakmap "^2.0.1" - is-weakset "^2.0.1" - which-module@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho= -which-typed-array@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.2.tgz#e5f98e56bda93e3dac196b01d47c1156679c00b2" - integrity sha512-KT6okrd1tE6JdZAy3o2VhMoYPh3+J6EMZLyrxBQsZflI1QCZIxMrIYLkosd8Twf+YfknVIHmYQPgJt238p8dnQ== - dependencies: - available-typed-arrays "^1.0.2" - es-abstract "^1.17.5" - foreach "^2.0.5" - function-bind "^1.1.1" - has-symbols "^1.0.1" - is-typed-array "^1.1.3" - which@^1.2.9, which@^1.3.0: version "1.3.1" resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" From 7f5c3a4688cea7512063cb415b66c2f8ec882e1c Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Mon, 16 Nov 2020 18:04:44 +0000 Subject: [PATCH 07/15] Updating to have proper access control via an accessController and nearly ready to spit out the routing structure. --- .../server/src/api/controllers/application.js | 2 +- .../server/src/api/controllers/routing.js | 18 +++- packages/server/src/api/controllers/screen.js | 29 +++++-- packages/server/src/api/index.js | 74 ++-------------- packages/server/src/api/routes/index.js | 9 +- packages/server/src/api/routes/routing.js | 5 +- packages/server/src/api/routes/screen.js | 6 +- packages/server/src/middleware/authorized.js | 2 - .../src/{ => utilities}/routing/index.js | 9 +- .../{ => utilities}/routing/routingUtils.js | 11 +-- .../src/utilities/security/accessLevels.js | 84 ++++++++++++++----- 11 files changed, 132 insertions(+), 117 deletions(-) rename packages/server/src/{ => utilities}/routing/index.js (76%) rename packages/server/src/{ => utilities}/routing/routingUtils.js (60%) diff --git a/packages/server/src/api/controllers/application.js b/packages/server/src/api/controllers/application.js index 59fb10a5b6..dd44bda69a 100644 --- a/packages/server/src/api/controllers/application.js +++ b/packages/server/src/api/controllers/application.js @@ -8,7 +8,7 @@ const fs = require("fs-extra") const { join, resolve } = require("../../utilities/centralPath") const packageJson = require("../../../package.json") const { createLinkView } = require("../../db/linkedRows") -const { createRoutingView } = require("../../routing") +const { createRoutingView } = require("../../utilities/routing") const { downloadTemplate } = require("../../utilities/templates") const { generateAppID, diff --git a/packages/server/src/api/controllers/routing.js b/packages/server/src/api/controllers/routing.js index ec2458a8ca..3be2e3fd41 100644 --- a/packages/server/src/api/controllers/routing.js +++ b/packages/server/src/api/controllers/routing.js @@ -1 +1,17 @@ -exports.fetch = async ctx => {} +const { getRoutingInfo } = require("../../utilities/routing") +const { AccessController } = require("../../utilities/security/accessLevels") + +async function getRoutingStructure(appId) { + let baseRouting = await getRoutingInfo(appId) + return baseRouting +} + +exports.fetch = async ctx => { + ctx.body = await getRoutingStructure(ctx.appId) +} + +exports.clientFetch = async ctx => { + const routing = getRoutingStructure(ctx.appId) + // use the access controller to pick which access level is applicable to this user + const accessController = new AccessController(ctx.appId) +} diff --git a/packages/server/src/api/controllers/screen.js b/packages/server/src/api/controllers/screen.js index 88166bf0b2..694d171fff 100644 --- a/packages/server/src/api/controllers/screen.js +++ b/packages/server/src/api/controllers/screen.js @@ -1,20 +1,28 @@ const CouchDB = require("../../db") const { getScreenParams, generateScreenID } = require("../../db/utils") +const { AccessController } = require("../../utilities/security/accessLevels") exports.fetch = async ctx => { - const db = new CouchDB(ctx.user.appId) + const appId = ctx.user.appId + const db = new CouchDB(appId) - const screens = await db.allDocs( - getScreenParams(null, { - include_docs: true, - }) + const screens = ( + await db.allDocs( + getScreenParams(null, { + include_docs: true, + }) + ) + ).rows.map(element => element.doc) + + ctx.body = await new AccessController(appId).checkScreensAccess( + screens, + ctx.user.accessLevel._id ) - - ctx.body = screens.rows.map(element => element.doc) } exports.find = async ctx => { - const db = new CouchDB(ctx.user.appId) + const appId = ctx.user.appId + const db = new CouchDB(appId) const screens = await db.allDocs( getScreenParams(ctx.params.pageId, { @@ -22,7 +30,10 @@ exports.find = async ctx => { }) ) - ctx.body = screens.response.rows + ctx.body = await new AccessController(appId).checkScreensAccess( + screens, + ctx.user.accessLevel._id + ) } exports.save = async ctx => { diff --git a/packages/server/src/api/index.js b/packages/server/src/api/index.js index 0d25e08dee..500ca50ff2 100644 --- a/packages/server/src/api/index.js +++ b/packages/server/src/api/index.js @@ -4,26 +4,7 @@ const compress = require("koa-compress") const zlib = require("zlib") const { budibaseAppsDir } = require("../utilities/budibaseDir") const { isDev } = require("../utilities") -const { - authRoutes, - pageRoutes, - screenRoutes, - userRoutes, - deployRoutes, - applicationRoutes, - rowRoutes, - tableRoutes, - viewRoutes, - staticRoutes, - componentRoutes, - automationRoutes, - accesslevelRoutes, - apiKeysRoutes, - templatesRoutes, - analyticsRoutes, - webhookRoutes, - routingRoutes, -} = require("./routes") +const {mainRoutes, authRoutes, staticRoutes} = require("./routes") const router = new Router() const env = require("../environment") @@ -73,58 +54,15 @@ router.use(authRoutes.routes()) router.use(authRoutes.allowedMethods()) // authenticated routes -router.use(viewRoutes.routes()) -router.use(viewRoutes.allowedMethods()) - -router.use(tableRoutes.routes()) -router.use(tableRoutes.allowedMethods()) - -router.use(rowRoutes.routes()) -router.use(rowRoutes.allowedMethods()) - -router.use(userRoutes.routes()) -router.use(userRoutes.allowedMethods()) - -router.use(automationRoutes.routes()) -router.use(automationRoutes.allowedMethods()) - -router.use(webhookRoutes.routes()) -router.use(webhookRoutes.allowedMethods()) - -router.use(deployRoutes.routes()) -router.use(deployRoutes.allowedMethods()) - -router.use(templatesRoutes.routes()) -router.use(templatesRoutes.allowedMethods()) -// end auth routes - -router.use(pageRoutes.routes()) -router.use(pageRoutes.allowedMethods()) - -router.use(screenRoutes.routes()) -router.use(screenRoutes.allowedMethods()) - -router.use(applicationRoutes.routes()) -router.use(applicationRoutes.allowedMethods()) - -router.use(componentRoutes.routes()) -router.use(componentRoutes.allowedMethods()) - -router.use(accesslevelRoutes.routes()) -router.use(accesslevelRoutes.allowedMethods()) - -router.use(apiKeysRoutes.routes()) -router.use(apiKeysRoutes.allowedMethods()) - -router.use(analyticsRoutes.routes()) -router.use(analyticsRoutes.allowedMethods()) +for (let route of mainRoutes) { + router.use(route.routes()) + router.use(route.allowedMethods()) +} +// WARNING - static routes will catch everything else after them this must be last router.use(staticRoutes.routes()) router.use(staticRoutes.allowedMethods()) -router.use(routingRoutes.routes()) -router.use(routingRoutes.allowedMethods()) - router.redirect("/", "/_builder") module.exports = router diff --git a/packages/server/src/api/routes/index.js b/packages/server/src/api/routes/index.js index 2352b2edc2..44f2d08509 100644 --- a/packages/server/src/api/routes/index.js +++ b/packages/server/src/api/routes/index.js @@ -17,9 +17,8 @@ const templatesRoutes = require("./templates") const analyticsRoutes = require("./analytics") const routingRoutes = require("./routing") -module.exports = { +exports.mainRoutes = [ deployRoutes, - authRoutes, pageRoutes, screenRoutes, userRoutes, @@ -27,7 +26,6 @@ module.exports = { rowRoutes, tableRoutes, viewRoutes, - staticRoutes, componentRoutes, automationRoutes, accesslevelRoutes, @@ -36,4 +34,7 @@ module.exports = { analyticsRoutes, webhookRoutes, routingRoutes, -} +] + +exports.authRoutes = authRoutes +exports.staticRoutes = staticRoutes diff --git a/packages/server/src/api/routes/routing.js b/packages/server/src/api/routes/routing.js index f336e4ed67..60f84de781 100644 --- a/packages/server/src/api/routes/routing.js +++ b/packages/server/src/api/routes/routing.js @@ -5,6 +5,9 @@ const controller = require("../controllers/routing") const router = Router() -router.post("/api/routing", authorized(BUILDER), controller.fetch) +// gets the full structure, not just the correct screen ID for your access level +router + .get("/api/routing", authorized(BUILDER), controller.fetch) + .get("/api/routing/client", controller.clientFetch) module.exports = router diff --git a/packages/server/src/api/routes/screen.js b/packages/server/src/api/routes/screen.js index 9bfd7a3973..ce49f66043 100644 --- a/packages/server/src/api/routes/screen.js +++ b/packages/server/src/api/routes/screen.js @@ -12,10 +12,10 @@ function generateSaveValidation() { return joiValidator.body(Joi.object({ _css: Joi.string().allow(""), name: Joi.string().required(), - routing: Joi.array().items(Joi.object({ + routing: Joi.object({ route: Joi.string().required(), - accessLevelId: Joi.string().required(), - })).required(), + accessLevelId: Joi.string().required().allow(""), + }).required().unknown(true), props: Joi.object({ _id: Joi.string().required(), _component: Joi.string().required(), diff --git a/packages/server/src/middleware/authorized.js b/packages/server/src/middleware/authorized.js index 5b36448463..5f4b78b97e 100644 --- a/packages/server/src/middleware/authorized.js +++ b/packages/server/src/middleware/authorized.js @@ -53,8 +53,6 @@ module.exports = (permType, permLevel = null) => async (ctx, next) => { return next() } - // TODO: need to handle routing security - if (permType === PermissionTypes.BUILDER) { ctx.throw(403, "Not Authorized") } diff --git a/packages/server/src/routing/index.js b/packages/server/src/utilities/routing/index.js similarity index 76% rename from packages/server/src/routing/index.js rename to packages/server/src/utilities/routing/index.js index 5548f45723..bb0fe5bb62 100644 --- a/packages/server/src/routing/index.js +++ b/packages/server/src/utilities/routing/index.js @@ -1,11 +1,14 @@ -const CouchDB = require("../db") +const CouchDB = require("../../db") const { createRoutingView } = require("./routingUtils") -const { ViewNames, getQueryIndex } = require("../db/utils") +const { ViewNames, getQueryIndex, UNICODE_MAX } = require("../../db/utils") exports.getRoutingInfo = async appId => { const db = new CouchDB(appId) try { - const allRouting = await db.query(getQueryIndex(ViewNames.ROUTING)) + const allRouting = await db.query(getQueryIndex(ViewNames.ROUTING), { + startKey: "", + endKey: UNICODE_MAX, + }) return allRouting.rows.map(row => row.value) } catch (err) { // check if the view doesn't exist, it should for all new instances diff --git a/packages/server/src/routing/routingUtils.js b/packages/server/src/utilities/routing/routingUtils.js similarity index 60% rename from packages/server/src/routing/routingUtils.js rename to packages/server/src/utilities/routing/routingUtils.js index e5a610e70d..5f6a6b5312 100644 --- a/packages/server/src/routing/routingUtils.js +++ b/packages/server/src/utilities/routing/routingUtils.js @@ -1,19 +1,20 @@ -const CouchDB = require("../db") -const { DocumentTypes, SEPARATOR, ViewNames } = require("../db/utils") +const CouchDB = require("../../db") +const { DocumentTypes, SEPARATOR, ViewNames } = require("../../db/utils") const SCREEN_PREFIX = DocumentTypes.SCREEN + SEPARATOR exports.createRoutingView = async appId => { const db = new CouchDB(appId) const designDoc = await db.get("_design/database") const view = { - map: function(doc) { - if (doc._id.startsWith(SCREEN_PREFIX)) { + // if using variables in a map function need to inject them before use + map: `function(doc) { + if (doc._id.startsWith("${SCREEN_PREFIX}")) { emit(doc._id, { id: doc._id, routing: doc.routing, }) } - }.toString(), + }`, } designDoc.views = { ...designDoc.views, diff --git a/packages/server/src/utilities/security/accessLevels.js b/packages/server/src/utilities/security/accessLevels.js index a77d9bc6aa..e99b635f39 100644 --- a/packages/server/src/utilities/security/accessLevels.js +++ b/packages/server/src/utilities/security/accessLevels.js @@ -33,36 +33,80 @@ exports.BUILTIN_LEVEL_NAME_ARRAY = Object.values(exports.BUILTIN_LEVELS).map( ) function isBuiltin(accessLevel) { - return BUILTIN_IDS.indexOf(accessLevel) !== -1 + return exports.BUILTIN_LEVEL_ID_ARRAY.indexOf(accessLevel) !== -1 } -exports.getAccessLevel = async (appId, accessLevelId) => { - if (isBuiltin(accessLevelId)) { - return Object.values(exports.BUILTIN_LEVELS).find( - level => level._id === accessLevelId - ) +class AccessController { + constructor(appId) { + this.appId = appId + this.accessLevels = {} } - const db = new CouchDB(appId) - return await db.get(accessLevelId) -} -exports.hasAccess = async (appId, tryingAccessLevelId, userAccessLevelId) => { - // special first case, if they are equal then access is allowed, no need to try anything - if (tryingAccessLevelId === userAccessLevelId) { - return true + async getAccessLevel(accessLevelId) { + if (this.accessLevels[accessLevelId]) { + return this.accessLevels[accessLevelId] + } + let accessLevel + if (isBuiltin(accessLevelId)) { + accessLevel = Object.values(exports.BUILTIN_LEVELS).find( + level => level._id === accessLevelId + ) + } else { + const db = new CouchDB(this.appId) + accessLevel = await db.get(accessLevelId) + } + this.accessLevels[accessLevelId] = accessLevel + return accessLevel } - let userAccess = await exports.getAccessLevel(appId, userAccessLevelId) - // check if inherited makes it possible - while (userAccess.inherits) { - if (tryingAccessLevelId === userAccess.inherits) { + + async hasAccess(tryingAccessLevelId, userAccessLevelId) { + // special cases, the screen has no access level, the access levels are the same or the user + // is currently in the builder + if ( + tryingAccessLevelId == null || + tryingAccessLevelId === "" || + tryingAccessLevelId === userAccessLevelId || + userAccessLevelId === BUILTIN_IDS.BUILDER + ) { return true } - // go to get the inherited incase it inherits anything - userAccess = await exports.getAccessLevel(appId, userAccess.inherits) + let userAccess = await this.getAccessLevel(userAccessLevelId) + // check if inherited makes it possible + while (userAccess.inherits) { + if (tryingAccessLevelId === userAccess.inherits) { + return true + } + // go to get the inherited incase it inherits anything + userAccess = await this.getAccessLevel(userAccess.inherits) + } + return false + } + + async checkScreensAccess(screens, userAccessLevelId) { + let accessibleScreens = [] + // don't want to handle this with Promise.all as this would mean all custom access levels would be + // retrieved at same time, it is likely a custom levels will be re-used and therefore want + // to work in sync for performance save + for (let screen of screens) { + const accessible = await this.checkScreenAccess(screen, userAccessLevelId) + if (accessible) { + accessibleScreens.push(accessible) + } + } + return accessibleScreens + } + + async checkScreenAccess(screen, userAccessLevelId) { + const accessLevelId = + screen && screen.routing ? screen.routing.accessLevelId : null + if (await this.hasAccess(accessLevelId, userAccessLevelId)) { + return screen + } + return null } - return false } +exports.AccessController = AccessController exports.BUILTIN_LEVEL_IDS = BUILTIN_IDS exports.isBuiltin = isBuiltin exports.AccessLevel = AccessLevel From f162ce6f033592e78b522ff7fc73371124b42c0f Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Mon, 16 Nov 2020 18:05:17 +0000 Subject: [PATCH 08/15] Attempting to create a class structure for screen and component templates as right now modifying the templates is quite difficult. --- packages/builder/package.json | 3 +- .../src/builderStore/store/frontend.js | 1 + .../createFromScratchScreen.js | 25 +- .../screenTemplates/emptyNewRowScreen.js | 25 +- .../screenTemplates/emptyRowDetailScreen.js | 25 +- .../store/screenTemplates/newRowScreen.js | 368 +++++++----------- .../store/screenTemplates/rowDetailScreen.js | 7 +- .../store/screenTemplates/rowListScreen.js | 7 +- .../screenTemplates/utils/BaseStructure.js | 36 ++ .../store/screenTemplates/utils/Component.js | 52 +++ .../store/screenTemplates/utils/Screen.js | 56 +++ .../screenTemplates/utils/commonComponents.js | 22 ++ .../{ => utils}/sanitizeUrl.js | 0 packages/builder/yarn.lock | 203 +++++++++- 14 files changed, 533 insertions(+), 297 deletions(-) create mode 100644 packages/builder/src/builderStore/store/screenTemplates/utils/BaseStructure.js create mode 100644 packages/builder/src/builderStore/store/screenTemplates/utils/Component.js create mode 100644 packages/builder/src/builderStore/store/screenTemplates/utils/Screen.js create mode 100644 packages/builder/src/builderStore/store/screenTemplates/utils/commonComponents.js rename packages/builder/src/builderStore/store/screenTemplates/{ => utils}/sanitizeUrl.js (100%) diff --git a/packages/builder/package.json b/packages/builder/package.json index 226d69f863..44e7897a15 100644 --- a/packages/builder/package.json +++ b/packages/builder/package.json @@ -81,7 +81,8 @@ "shortid": "^2.2.15", "svelte-loading-spinners": "^0.1.1", "svelte-portal": "^0.1.0", - "yup": "^0.29.2" + "yup": "^0.29.2", + "uuid": "^8.3.1" }, "devDependencies": { "@babel/core": "^7.5.5", diff --git a/packages/builder/src/builderStore/store/frontend.js b/packages/builder/src/builderStore/store/frontend.js index 35cd8b09f6..bac3ba84ba 100644 --- a/packages/builder/src/builderStore/store/frontend.js +++ b/packages/builder/src/builderStore/store/frontend.js @@ -50,6 +50,7 @@ export const getFrontendStore = () => { return state }) const screens = await api.get("/api/screens").then(r => r.json()) + const routing = await api.get("/api/routing").then(r => r.json()) const mainScreens = screens.filter(screen => screen._id.includes(pkg.pages.main._id) diff --git a/packages/builder/src/builderStore/store/screenTemplates/createFromScratchScreen.js b/packages/builder/src/builderStore/store/screenTemplates/createFromScratchScreen.js index a8ab27df3d..b25562758e 100644 --- a/packages/builder/src/builderStore/store/screenTemplates/createFromScratchScreen.js +++ b/packages/builder/src/builderStore/store/screenTemplates/createFromScratchScreen.js @@ -1,22 +1,13 @@ +import { Screen } from "./utils/Screen" + export default { name: `Create from scratch`, create: () => createScreen(), } -const createScreen = () => ({ - props: { - _id: "", - _component: "@budibase/standard-components/container", - _styles: { - normal: {}, - hover: {}, - active: {}, - selected: {}, - }, - type: "div", - _children: [], - _instanceName: "", - }, - route: "", - name: "screen-id", -}) +const createScreen = () => { + return new Screen() + .mainType("div") + .component("@budibase/standard-components/container") + .json() +} diff --git a/packages/builder/src/builderStore/store/screenTemplates/emptyNewRowScreen.js b/packages/builder/src/builderStore/store/screenTemplates/emptyNewRowScreen.js index e58319688b..a2f2f6df67 100644 --- a/packages/builder/src/builderStore/store/screenTemplates/emptyNewRowScreen.js +++ b/packages/builder/src/builderStore/store/screenTemplates/emptyNewRowScreen.js @@ -1,22 +1,13 @@ +import { Screen } from "./utils/Screen" + export default { name: `New Row (Empty)`, create: () => createScreen(), } -const createScreen = () => ({ - props: { - _id: "", - _component: "@budibase/standard-components/newrow", - _styles: { - normal: {}, - hover: {}, - active: {}, - selected: {}, - }, - _children: [], - _instanceName: "", - table: "", - }, - route: "", - name: "screen-id", -}) +const createScreen = () => { + return new Screen() + .component("@budibase/standard-components/newrow") + .table("") + .json() +} diff --git a/packages/builder/src/builderStore/store/screenTemplates/emptyRowDetailScreen.js b/packages/builder/src/builderStore/store/screenTemplates/emptyRowDetailScreen.js index a75de583cb..5dbdcf4e69 100644 --- a/packages/builder/src/builderStore/store/screenTemplates/emptyRowDetailScreen.js +++ b/packages/builder/src/builderStore/store/screenTemplates/emptyRowDetailScreen.js @@ -1,22 +1,13 @@ +import { Screen } from "./utils/Screen" + export default { name: `Row Detail (Empty)`, create: () => createScreen(), } -const createScreen = () => ({ - props: { - _id: "", - _component: "@budibase/standard-components/rowdetail", - _styles: { - normal: {}, - hover: {}, - active: {}, - selected: {}, - }, - _children: [], - _instanceName: "", - table: "", - }, - route: "", - name: "screen-id", -}) +const createScreen = () => { + return new Screen() + .component("@budibase/standard-components/rowdetail") + .table("") + .json() +} diff --git a/packages/builder/src/builderStore/store/screenTemplates/newRowScreen.js b/packages/builder/src/builderStore/store/screenTemplates/newRowScreen.js index 50e90cddcf..58fb4445a2 100644 --- a/packages/builder/src/builderStore/store/screenTemplates/newRowScreen.js +++ b/packages/builder/src/builderStore/store/screenTemplates/newRowScreen.js @@ -1,5 +1,8 @@ -import sanitizeUrl from "./sanitizeUrl" +import sanitizeUrl from "./utils/sanitizeUrl" import { rowListUrl } from "./rowListScreen" +import { Component } from "./utils/Component" +import { Screen } from "./utils/Screen" +import { linkComponent } from "./utils/commonComponents" export default function(tables) { return tables.map(table => { @@ -14,242 +17,133 @@ export default function(tables) { export const newRowUrl = table => sanitizeUrl(`/${table.name}/new`) export const NEW_ROW_TEMPLATE = "NEW_ROW_TEMPLATE" -const createScreen = table => ({ - props: { - _id: "c683c4ca8ffc849c6bdd3b7d637fbbf3c", - _component: "@budibase/standard-components/newrow", - _styles: { - normal: {}, - hover: {}, - active: {}, - selected: {}, - }, - table: table._id, - _children: [ - { - _id: "ccad6cc135c7947a7ba9c631f655d6e0f", - _component: "@budibase/standard-components/container", - _styles: { - normal: { - width: "700px", - padding: "0px", - background: "white", - "border-radius": "0.5rem", - "box-shadow": "0 1px 2px 0 rgba(0, 0, 0, 0.05)", - margin: "auto", - "margin-top": "20px", - "padding-top": "48px", - "padding-bottom": "48px", - "padding-right": "48px", - "padding-left": "48px", - "margin-bottom": "20px", +function breadcrumbContainer(table) { + const link = linkComponent(table.name).instanceName("Back Link") + + const arrowText = new Component("@budibase/standard-components/text") + .type("none") + .normalStyle({ + "margin-right": "4px", + "margin-left": "4px", + }) + .text(">") + .instanceName("Arrow") + + const newText = new Component("@budibase/standard-components/text") + .type("none") + .normalStyle({ + color: "#000000", + }) + .text("New") + .instanceName("Identifier") + + return new Component("@budibase/standard-components/container") + .type("div") + .normalStyle({ + "font-size": "14px", + color: "#757575", + }) + .instanceName("Breadcrumbs") + .addChild(link) + .addChild(arrowText) + .addChild(newText) +} + +function titleContainer(table) { + const heading = new Component("@budibase/standard-components/heading") + .normalStyle({ + margin: "0px", + "margin-bottom": "0px", + "margin-right": "0px", + "margin-top": "0px", + "margin-left": "0px", + flex: "1 1 auto", + }) + .type("h3") + .instanceName("Title") + .text("New Row") + + const button = new Component("@budibase/standard-components/button") + .normalStyle({ + background: "#000000", + "border-width": "0", + "border-style": "None", + color: "#fff", + "font-family": "Inter", + "font-weight": "500", + "font-size": "14px", + "margin-left": "16px", + }) + .hoverStyle({ + background: "#4285f4", + }) + .text("Save") + .customProps({ + className: "", + disabled: false, + onClick: [ + { + parameters: { + contextPath: "data", + tableId: table._id, }, - hover: {}, - active: {}, - selected: {}, + "##eventHandlerType": "Save Row", }, - _code: "", - className: "", - onLoad: [], - type: "div", - _instanceId: "inst_app_8fb_631af42f9dc94da2b5c48dc6c5124610", - _instanceName: "Container", - _children: [ - { - _id: "c6e91622ba7984f468f70bf4bf5120246", - _component: "@budibase/standard-components/container", - _styles: { - normal: { - "font-size": "14px", - color: "#757575", - }, - hover: {}, - active: {}, - selected: {}, - }, - _code: "", - className: "", - onLoad: [], - type: "div", - _instanceId: "inst_app_8fb_631af42f9dc94da2b5c48dc6c5124610", - _instanceName: "Breadcrumbs", - _children: [ - { - _id: "caa33353c252c4931b2a51b48a559a7fc", - _component: "@budibase/standard-components/link", - _styles: { - normal: { - color: "#757575", - "text-transform": "capitalize", - }, - hover: { - color: "#4285f4", - }, - active: {}, - selected: {}, - }, - _code: "", - url: `/${table.name.toLowerCase()}`, - openInNewTab: false, - text: table.name, - color: "", - hoverColor: "", - underline: false, - fontSize: "", - fontFamily: "initial", - _instanceId: "inst_app_8fb_631af42f9dc94da2b5c48dc6c5124610", - _instanceName: "Back Link", - _children: [], - }, - { - _id: "c6e218170201040e7a74e2c8304fe1860", - _component: "@budibase/standard-components/text", - _styles: { - normal: { - "margin-right": "4px", - "margin-left": "4px", - }, - hover: {}, - active: {}, - selected: {}, - }, - _code: "", - text: ">", - type: "none", - _instanceId: "inst_app_8fb_631af42f9dc94da2b5c48dc6c5124610", - _instanceName: "Arrow", - _children: [], - }, - { - _id: "c799da1fa3a84442e947cc9199518f64c", - _component: "@budibase/standard-components/text", - _styles: { - normal: { - color: "#000000", - }, - hover: {}, - active: {}, - selected: {}, - }, - _code: "", - text: "New", - type: "none", - _instanceId: "inst_app_8fb_631af42f9dc94da2b5c48dc6c5124610", - _instanceName: "Identifier", - _children: [], - }, - ], + { + parameters: { + url: rowListUrl(table), }, - { - _id: "cbd1637cd1e274287a3c28ef0bf235d08", - _component: "@budibase/standard-components/container", - _styles: { - normal: { - display: "flex", - "flex-direction": "row", - "justify-content": "space-between", - "align-items": "center", - "margin-top": "32px", - "margin-bottom": "32px", - }, - hover: {}, - active: {}, - selected: {}, - }, - _code: "", - className: "", - onLoad: [], - type: "div", - _instanceId: "inst_app_8fb_631af42f9dc94da2b5c48dc6c5124610", - _instanceName: "Title Container", - _children: [ - { - _id: "c98d3675d04114558bbf28661c5ccfb8e", - _component: "@budibase/standard-components/heading", - _styles: { - normal: { - margin: "0px", - "margin-bottom": "0px", - "margin-right": "0px", - "margin-top": "0px", - "margin-left": "0px", - flex: "1 1 auto", - }, - hover: {}, - active: {}, - selected: {}, - }, - _code: "", - className: "", - text: "New Row", - type: "h3", - _instanceName: "Title", - _children: [], - }, - { - _id: "cae402bd3c6a44618a8341bf7ab9ab086", - _component: "@budibase/standard-components/button", - _styles: { - normal: { - background: "#000000", - "border-width": "0", - "border-style": "None", - color: "#fff", - "font-family": "Inter", - "font-weight": "500", - "font-size": "14px", - "margin-left": "16px", - }, - hover: { - background: "#4285f4", - }, - active: {}, - selected: {}, - }, - _code: "", - text: "Save", - className: "", - disabled: false, - onClick: [ - { - parameters: { - contextPath: "data", - tableId: table._id, - }, - "##eventHandlerType": "Save Row", - }, - { - parameters: { - url: rowListUrl(table), - }, - "##eventHandlerType": "Navigate To", - }, - ], - _instanceName: "Save Button", - _children: [], - }, - ], - }, - { - _id: "c5e6c98d7363640f9ad3a7d19c8c10f67", - _component: "@budibase/standard-components/dataformwide", - _styles: { - normal: {}, - hover: {}, - active: {}, - selected: {}, - }, - _code: "", - _instanceId: "inst_app_8fb_631af42f9dc94da2b5c48dc6c5124610", - _instanceName: "Form", - _children: [], - }, - ], - }, - ], - _instanceName: `${table.name} - New`, - _code: "", - }, - route: newRowUrl(table), - name: "", -}) + "##eventHandlerType": "Navigate To", + }, + ], + }) + .instanceName("Save Button") + + return new Component("@budibase/standard-components/container") + .type("div") + .normalStyle({ + display: "flex", + "flex-direction": "row", + "justify-content": "space-between", + "align-items": "center", + "margin-top": "32px", + "margin-bottom": "32px", + }) + .instanceName("Title Container") + .addChild(heading) + .addChild(button) +} + +const createScreen = table => { + const dataform = new Component("@budibase/standard-components/dataformwide") + .instanceName("Form") + + const mainContainer = new Component("@budibase/standard-components/container") + .type("div") + .normalStyle({ + width: "700px", + padding: "0px", + background: "white", + "border-radius": "0.5rem", + "box-shadow": "0 1px 2px 0 rgba(0, 0, 0, 0.05)", + margin: "auto", + "margin-top": "20px", + "padding-top": "48px", + "padding-bottom": "48px", + "padding-right": "48px", + "padding-left": "48px", + "margin-bottom": "20px", + }) + .instanceName("Container") + .addChild(breadcrumbContainer(table)) + .addChild(titleContainer(table)) + .addChild(dataform) + + return new Screen().component("@budibase/standard-components/newrow") + .addChild(mainContainer) + .table(table._id) + .route(newRowUrl(table)) + .instanceName(`${table.name} - New`) + .name("") + .json() +} diff --git a/packages/builder/src/builderStore/store/screenTemplates/rowDetailScreen.js b/packages/builder/src/builderStore/store/screenTemplates/rowDetailScreen.js index a4f55f2fd1..2ec985b90a 100644 --- a/packages/builder/src/builderStore/store/screenTemplates/rowDetailScreen.js +++ b/packages/builder/src/builderStore/store/screenTemplates/rowDetailScreen.js @@ -1,4 +1,4 @@ -import sanitizeUrl from "./sanitizeUrl" +import sanitizeUrl from "./utils/sanitizeUrl" import { rowListUrl } from "./rowListScreen" export default function(tables) { @@ -299,6 +299,9 @@ const createScreen = (table, heading) => ({ _instanceName: `${table.name} - Detail`, _code: "", }, - route: rowDetailUrl(table), + routing: { + route: rowDetailUrl(table), + accessLevelId: "", + }, name: "", }) diff --git a/packages/builder/src/builderStore/store/screenTemplates/rowListScreen.js b/packages/builder/src/builderStore/store/screenTemplates/rowListScreen.js index 5c71a45f1f..d9243b9b2f 100644 --- a/packages/builder/src/builderStore/store/screenTemplates/rowListScreen.js +++ b/packages/builder/src/builderStore/store/screenTemplates/rowListScreen.js @@ -1,4 +1,4 @@ -import sanitizeUrl from "./sanitizeUrl" +import sanitizeUrl from "./utils/sanitizeUrl" import { newRowUrl } from "./newRowScreen" export default function(tables) { @@ -167,6 +167,9 @@ const createScreen = table => ({ className: "", onLoad: [], }, - route: rowListUrl(table), + routing: { + route: rowListUrl(table), + accessLevelId: "", + }, name: "", }) diff --git a/packages/builder/src/builderStore/store/screenTemplates/utils/BaseStructure.js b/packages/builder/src/builderStore/store/screenTemplates/utils/BaseStructure.js new file mode 100644 index 0000000000..7b2bb8e927 --- /dev/null +++ b/packages/builder/src/builderStore/store/screenTemplates/utils/BaseStructure.js @@ -0,0 +1,36 @@ +import { cloneDeep } from "lodash/fp" + +export class BaseStructure { + constructor(isScreen) { + this._isScreen = isScreen + this._children = [] + this._json = { + } + } + + addChild(child) { + this._children.push(child) + return this + } + + customProps(props) { + for (let key of Object.keys(props)) { + this._json[key] = props[key] + } + return this + } + + json() { + const structure = cloneDeep(this._json) + if (this._children.length !== 0) { + for (let child of this._children) { + if (this._isScreen) { + structure.props._children.push(child.json()) + } else { + structure._children.push(child.json()) + } + } + } + return structure + } +} diff --git a/packages/builder/src/builderStore/store/screenTemplates/utils/Component.js b/packages/builder/src/builderStore/store/screenTemplates/utils/Component.js new file mode 100644 index 0000000000..27b4af2d5b --- /dev/null +++ b/packages/builder/src/builderStore/store/screenTemplates/utils/Component.js @@ -0,0 +1,52 @@ +import { cloneDeep } from "lodash/fp" +import { v4 } from "uuid" +import { BaseStructure } from "./BaseStructure" + +export class Component extends BaseStructure { + constructor(name) { + super(false) + this._children = [] + this._json = { + _id: v4(), + _component: name, + _styles: { + normal: {}, + hover: {}, + active: {}, + selected: {}, + }, + _code: "", + className: "", + onLoad: [], + type: "", + _instanceName: "", + _children: [], + } + } + + type(type) { + this._json.type = type + return this + } + + normalStyle(styling) { + this._json._styles.normal = styling + return this + } + + hoverStyle(styling) { + this._json._styles.hover = styling + return this + } + + text(text) { + this._json.text = text + return this + } + + // TODO: do we need this + instanceName(name) { + this._json._instanceName = name + return this + } +} diff --git a/packages/builder/src/builderStore/store/screenTemplates/utils/Screen.js b/packages/builder/src/builderStore/store/screenTemplates/utils/Screen.js new file mode 100644 index 0000000000..951e26aeb6 --- /dev/null +++ b/packages/builder/src/builderStore/store/screenTemplates/utils/Screen.js @@ -0,0 +1,56 @@ +import { BaseStructure } from "./BaseStructure" + +export class Screen extends BaseStructure { + constructor() { + super(true) + this._json = { + props: { + _id: "", + _component: "", + _styles: { + normal: {}, + hover: {}, + active: {}, + selected: {}, + }, + _children: [], + _instanceName: "", + }, + routing: { + route: "", + accessLevelId: "", + }, + name: "screen-id", + } + } + + component(name) { + this._json.props._component = name + return this + } + + table(tableName) { + this._json.props.table = tableName + return this + } + + mainType(type) { + this._json.type = type + return this + } + + route(route) { + this._json.routing.route = route + return this + } + + name(name) { + this._json.name = name + return this + } + + instanceName(name) { + this._json.props._instanceName = name + return this + } +} diff --git a/packages/builder/src/builderStore/store/screenTemplates/utils/commonComponents.js b/packages/builder/src/builderStore/store/screenTemplates/utils/commonComponents.js new file mode 100644 index 0000000000..eae2d7f526 --- /dev/null +++ b/packages/builder/src/builderStore/store/screenTemplates/utils/commonComponents.js @@ -0,0 +1,22 @@ +import { Component } from "./Component" + +export function linkComponent(tableName) { + return new Component("@budibase/standard-components/link") + .normalStyle({ + color: "#757575", + "text-transform": "capitalize", + }) + .hoverStyle({ + color: "#4285f4", + }) + .text(tableName) + .customProps({ + url: `/${tableName.toLowerCase()}`, + openInNewTab: false, + color: "", + hoverColor: "", + underline: false, + fontSize: "", + fontFamily: "initial", + }) +} \ No newline at end of file diff --git a/packages/builder/src/builderStore/store/screenTemplates/sanitizeUrl.js b/packages/builder/src/builderStore/store/screenTemplates/utils/sanitizeUrl.js similarity index 100% rename from packages/builder/src/builderStore/store/screenTemplates/sanitizeUrl.js rename to packages/builder/src/builderStore/store/screenTemplates/utils/sanitizeUrl.js diff --git a/packages/builder/yarn.lock b/packages/builder/yarn.lock index 9c57c9b773..390506d2ca 100644 --- a/packages/builder/yarn.lock +++ b/packages/builder/yarn.lock @@ -719,6 +719,15 @@ svelte-flatpickr "^2.4.0" svelte-portal "^1.0.0" +"@budibase/client@^0.3.7": + version "0.3.8" + resolved "https://registry.yarnpkg.com/@budibase/client/-/client-0.3.8.tgz#75df7e97e8f0d9b58c00e2bb0d3b4a55f8d04735" + integrity sha512-tnFdmCdXKS+uZGoipr69Wa0oVoFHmyoV0ydihI6q0gKQH0KutypVHAaul2qPB8t5a/mTZopC//2WdmCeX1GKVg== + dependencies: + deep-equal "^2.0.1" + mustache "^4.0.1" + regexparam "^1.3.0" + "@budibase/colorpicker@^1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@budibase/colorpicker/-/colorpicker-1.0.1.tgz#940c180e7ebba0cb0756c4c8ef13f5dfab58e810" @@ -1407,6 +1416,11 @@ array-equal@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/array-equal/-/array-equal-1.0.0.tgz#8c2a5ef2472fd9ea742b04c77a75093ba2757c93" +array-filter@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/array-filter/-/array-filter-1.0.0.tgz#baf79e62e6ef4c2a4c0b831232daffec251f9d83" + integrity sha1-uveeYubvTCpMC4MSMtr/7CUfnYM= + array-union@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" @@ -1461,6 +1475,13 @@ atob@^2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" +available-typed-arrays@^1.0.0, available-typed-arrays@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.2.tgz#6b098ca9d8039079ee3f77f7b783c4480ba513f5" + integrity sha512-XWX3OX8Onv97LMk/ftVyBibpGwY5a8SmuxZPzeOxqmuEqUCOM9ZE+uIaD1VNJ5QnvU2UQusvmKbuM1FR8QWGfQ== + dependencies: + array-filter "^1.0.0" + aws-sign2@~0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" @@ -1732,6 +1753,14 @@ cachedir@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/cachedir/-/cachedir-2.3.0.tgz#0c75892a052198f0b21c7c1804d8331edfcae0e8" +call-bind@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.0.tgz#24127054bb3f9bdcb4b1fb82418186072f77b8ce" + integrity sha512-AEXsYIyyDY3MCzbwdhzG3Jx1R0J2wetQyUynn6dYHAO+bg8l1k7jwZtRv4ryryFs7EP+NDlikJlVe59jr0cM2w== + dependencies: + function-bind "^1.1.1" + get-intrinsic "^1.0.0" + callsites@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" @@ -2427,6 +2456,26 @@ deep-equal@^1.0.1: object-keys "^1.1.1" regexp.prototype.flags "^1.2.0" +deep-equal@^2.0.1: + version "2.0.4" + resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-2.0.4.tgz#6b0b407a074666033169df3acaf128e1c6f3eab6" + integrity sha512-BUfaXrVoCfgkOQY/b09QdO9L3XNoF2XH0A3aY9IQwQL/ZjLOe8FQgCNVl1wiolhsFo8kFdO9zdPViCPbmaJA5w== + dependencies: + es-abstract "^1.18.0-next.1" + es-get-iterator "^1.1.0" + is-arguments "^1.0.4" + is-date-object "^1.0.2" + is-regex "^1.1.1" + isarray "^2.0.5" + object-is "^1.1.3" + object-keys "^1.1.1" + object.assign "^4.1.1" + regexp.prototype.flags "^1.3.0" + side-channel "^1.0.3" + which-boxed-primitive "^1.0.1" + which-collection "^1.0.1" + which-typed-array "^1.1.2" + deep-is@~0.1.3: version "0.1.3" resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" @@ -2596,6 +2645,23 @@ es-abstract@^1.17.0-next.1, es-abstract@^1.17.2, es-abstract@^1.17.5: string.prototype.trimleft "^2.1.1" string.prototype.trimright "^2.1.1" +es-abstract@^1.17.4: + version "1.17.7" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.17.7.tgz#a4de61b2f66989fc7421676c1cb9787573ace54c" + integrity sha512-VBl/gnfcJ7OercKA9MVaegWsBHFjV492syMudcnQZvt/Dw8ezpcOHYZXa/J96O8vx+g4x65YKhxOwDUh63aS5g== + dependencies: + es-to-primitive "^1.2.1" + function-bind "^1.1.1" + has "^1.0.3" + has-symbols "^1.0.1" + is-callable "^1.2.2" + is-regex "^1.1.1" + object-inspect "^1.8.0" + object-keys "^1.1.1" + object.assign "^4.1.1" + string.prototype.trimend "^1.0.1" + string.prototype.trimstart "^1.0.1" + es-abstract@^1.18.0-next.0, es-abstract@^1.18.0-next.1: version "1.18.0-next.1" resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.18.0-next.1.tgz#6e3a0a4bda717e5023ab3b8e90bec36108d22c68" @@ -2614,6 +2680,20 @@ es-abstract@^1.18.0-next.0, es-abstract@^1.18.0-next.1: string.prototype.trimend "^1.0.1" string.prototype.trimstart "^1.0.1" +es-get-iterator@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/es-get-iterator/-/es-get-iterator-1.1.1.tgz#b93ddd867af16d5118e00881396533c1c6647ad9" + integrity sha512-qorBw8Y7B15DVLaJWy6WdEV/ZkieBcu6QCq/xzWzGOKJqgG1j754vXRfZ3NY7HSShneqU43mPB4OkQBTkvHhFw== + dependencies: + call-bind "^1.0.0" + get-intrinsic "^1.0.1" + has-symbols "^1.0.1" + is-arguments "^1.0.4" + is-map "^2.0.1" + is-set "^2.0.1" + is-string "^1.0.5" + isarray "^2.0.5" + es-to-primitive@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a" @@ -2931,7 +3011,7 @@ for-in@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" -foreach@~2.0.1: +foreach@^2.0.5, foreach@~2.0.1: version "2.0.5" resolved "https://registry.yarnpkg.com/foreach/-/foreach-2.0.5.tgz#0bee005018aeb260d0a3af3ae658dd0136ec1b99" integrity sha1-C+4AUBiusmDQo6865ljdATbsG5k= @@ -3017,6 +3097,15 @@ get-caller-file@^2.0.1: version "2.0.5" resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" +get-intrinsic@^1.0.0, get-intrinsic@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.0.1.tgz#94a9768fcbdd0595a1c9273aacf4c89d075631be" + integrity sha512-ZnWP+AmS1VUaLgTRy47+zKtjTxz+0xMpx3I52i+aalBK1QP19ggLF3Db89KJX7kjfOfP2eoa01qc++GwPgufPg== + dependencies: + function-bind "^1.1.1" + has "^1.0.3" + has-symbols "^1.0.1" + get-port@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/get-port/-/get-port-3.2.0.tgz#dd7ce7de187c06c8bf353796ac71e099f0980ebc" @@ -3314,12 +3403,22 @@ is-arrayish@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" +is-bigint@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.0.tgz#73da8c33208d00f130e9b5e15d23eac9215601c4" + integrity sha512-t5mGUXC/xRheCK431ylNiSkGGpBp8bHENBcENTkDT6ppwPzEVxNGZRvgvmOEfbWkFhA7D2GEuE2mmQTr78sl2g== + is-binary-path@~2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" dependencies: binary-extensions "^2.0.0" +is-boolean-object@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.0.1.tgz#10edc0900dd127697a92f6f9807c7617d68ac48e" + integrity sha512-TqZuVwa/sppcrhUCAYkGBk7w0yxfQQnxq28fjkO53tnK9FQXmdwz2JS5+GjsWQ6RByES1K40nI+yDic5c9/aAQ== + is-buffer@^1.1.5: version "1.1.6" resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" @@ -3351,7 +3450,7 @@ is-data-descriptor@^1.0.0: dependencies: kind-of "^6.0.0" -is-date-object@^1.0.1: +is-date-object@^1.0.1, is-date-object@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.2.tgz#bda736f2cd8fd06d32844e7743bfa7494c3bfd7e" integrity sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g== @@ -3417,6 +3516,11 @@ is-installed-globally@^0.3.2: global-dirs "^2.0.1" is-path-inside "^3.0.1" +is-map@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-map/-/is-map-2.0.1.tgz#520dafc4307bb8ebc33b813de5ce7c9400d644a1" + integrity sha512-T/S49scO8plUiAOA2DBTBG3JHpn1yiw0kRp6dgiZ0v2/6twi5eiB0rHtHFH9ZIrvlWc6+4O+m4zg5+Z833aXgw== + is-module@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-module/-/is-module-1.0.0.tgz#3258fb69f78c14d5b815d664336b4cffb6441591" @@ -3426,6 +3530,11 @@ is-negative-zero@^2.0.0: resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.0.tgz#9553b121b0fac28869da9ed459e20c7543788461" integrity sha1-lVOxIbD6wohp2p7UWeIMdUN4hGE= +is-number-object@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.4.tgz#36ac95e741cf18b283fc1ddf5e83da798e3ec197" + integrity sha512-zohwelOAur+5uXtk8O3GPQ1eAcu4ZX3UwxQhUlfFFMNpUd83gXgjbhJh6HmB6LUNV/ieOLQuDwJO3dWJosUeMw== + is-number@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195" @@ -3489,6 +3598,11 @@ is-regex@^1.0.5: dependencies: has "^1.0.3" +is-set@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-set/-/is-set-2.0.1.tgz#d1604afdab1724986d30091575f54945da7e5f43" + integrity sha512-eJEzOtVyenDs1TMzSQ3kU3K+E0GUS9sno+F0OBT97xsgcJsF9nXMBtkT9/kut5JEpM7oL7X/0qxR17K3mcwIAA== + is-stream@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" @@ -3497,16 +3611,41 @@ is-stream@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.0.tgz#bde9c32680d6fae04129d6ac9d921ce7815f78e3" +is-string@^1.0.4, is-string@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.5.tgz#40493ed198ef3ff477b8c7f92f644ec82a5cd3a6" + integrity sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ== + is-symbol@^1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.3.tgz#38e1014b9e6329be0de9d24a414fd7441ec61937" dependencies: has-symbols "^1.0.1" +is-typed-array@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.3.tgz#a4ff5a5e672e1a55f99c7f54e59597af5c1df04d" + integrity sha512-BSYUBOK/HJibQ30wWkWold5txYwMUXQct9YHAQJr8fSwvZoiglcqB0pd7vEN23+Tsi9IUEjztdOSzl4qLVYGTQ== + dependencies: + available-typed-arrays "^1.0.0" + es-abstract "^1.17.4" + foreach "^2.0.5" + has-symbols "^1.0.1" + is-typedarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" +is-weakmap@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-weakmap/-/is-weakmap-2.0.1.tgz#5008b59bdc43b698201d18f62b37b2ca243e8cf2" + integrity sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA== + +is-weakset@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-weakset/-/is-weakset-2.0.1.tgz#e9a0af88dbd751589f5e50d80f4c98b780884f83" + integrity sha512-pi4vhbhVHGLxohUw7PhGsueT4vRGFoXhP7+RGN0jKIv9+8PWYCQTqtADngrxOm2g46hoH0+g8uZZBzMrvVGDmw== + is-windows@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" @@ -3527,6 +3666,11 @@ isarray@1.0.0, isarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" +isarray@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723" + integrity sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw== + isbuffer@~0.0.0: version "0.0.0" resolved "https://registry.yarnpkg.com/isbuffer/-/isbuffer-0.0.0.tgz#38c146d9df528b8bf9b0701c3d43cf12df3fc39b" @@ -4652,7 +4796,7 @@ object-inspect@^1.8.0: resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.8.0.tgz#df807e5ecf53a609cc6bfe93eac3cc7be5b3a9d0" integrity sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA== -object-is@^1.0.1: +object-is@^1.0.1, object-is@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.1.3.tgz#2e3b9e65560137455ee3bd62aec4d90a2ea1cc81" integrity sha512-teyqLvFWzLkq5B9ki8FVWA902UER2qkxmdA4nLf+wjOLAWgxzCWZNCxpDq9MvE8MmhWNr+I8w3BN49Vx36Y6Xg== @@ -5207,7 +5351,7 @@ regex-not@^1.0.0, regex-not@^1.0.2: extend-shallow "^3.0.2" safe-regex "^1.1.0" -regexp.prototype.flags@^1.2.0: +regexp.prototype.flags@^1.2.0, regexp.prototype.flags@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.3.0.tgz#7aba89b3c13a64509dabcf3ca8d9fbb9bdf5cb75" integrity sha512-2+Q0C5g951OlYlJz6yu5/M33IcsESLlLfsyIaLJaG4FA2r4yP8MvVMJUUP/fVBkSpbbbZlS5gynbEWLipiiXiQ== @@ -5215,6 +5359,11 @@ regexp.prototype.flags@^1.2.0: define-properties "^1.1.3" es-abstract "^1.17.0-next.1" +regexparam@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/regexparam/-/regexparam-1.3.0.tgz#2fe42c93e32a40eff6235d635e0ffa344b92965f" + integrity sha512-6IQpFBv6e5vz1QAqI+V4k8P2e/3gRrqfCJ9FI+O1FLQTO+Uz6RXZEZOPmTJ6hlGj7gkERzY5BRCv09whKP96/g== + regexpu-core@^4.7.0: version "4.7.0" resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-4.7.0.tgz#fcbf458c50431b0bb7b45d6967b8192d91f3d938" @@ -5628,6 +5777,14 @@ shortid@^2.2.15: dependencies: nanoid "^2.1.0" +side-channel@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.3.tgz#cdc46b057550bbab63706210838df5d4c19519c3" + integrity sha512-A6+ByhlLkksFoUepsGxfj5x1gTSrs+OydsRptUxeNCabQpCFUvcwIczgOigI8vhY/OJCnPnyE9rGiwgvr9cS1g== + dependencies: + es-abstract "^1.18.0-next.0" + object-inspect "^1.8.0" + signal-exit@^3.0.0, signal-exit@^3.0.2: version "3.0.3" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c" @@ -6255,6 +6412,11 @@ uuid@^3.3.2: version "3.4.0" resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" +uuid@^8.3.1: + version "8.3.1" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.1.tgz#2ba2e6ca000da60fce5a196954ab241131e05a31" + integrity sha512-FOmRr+FmWEIG8uhZv6C2bTgEVXsHk08kE7mPlrBbEe+c3r9pjceVPgupIfNIhc4yx55H69OXANrUaSuu9eInKg== + validate-npm-package-license@^3.0.1: version "3.0.4" resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a" @@ -6349,10 +6511,43 @@ whatwg-url@^8.0.0: tr46 "^2.0.2" webidl-conversions "^5.0.0" +which-boxed-primitive@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.1.tgz#cbe8f838ebe91ba2471bb69e9edbda67ab5a5ec1" + integrity sha512-7BT4TwISdDGBgaemWU0N0OU7FeAEJ9Oo2P1PHRm/FCWoEi2VLWC9b6xvxAA3C/NMpxg3HXVgi0sMmGbNUbNepQ== + dependencies: + is-bigint "^1.0.0" + is-boolean-object "^1.0.0" + is-number-object "^1.0.3" + is-string "^1.0.4" + is-symbol "^1.0.2" + +which-collection@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/which-collection/-/which-collection-1.0.1.tgz#70eab71ebbbd2aefaf32f917082fc62cdcb70906" + integrity sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A== + dependencies: + is-map "^2.0.1" + is-set "^2.0.1" + is-weakmap "^2.0.1" + is-weakset "^2.0.1" + which-module@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" +which-typed-array@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.2.tgz#e5f98e56bda93e3dac196b01d47c1156679c00b2" + integrity sha512-KT6okrd1tE6JdZAy3o2VhMoYPh3+J6EMZLyrxBQsZflI1QCZIxMrIYLkosd8Twf+YfknVIHmYQPgJt238p8dnQ== + dependencies: + available-typed-arrays "^1.0.2" + es-abstract "^1.17.5" + foreach "^2.0.5" + function-bind "^1.1.1" + has-symbols "^1.0.1" + is-typed-array "^1.1.3" + which@^1.2.9, which@^1.3.0: version "1.3.1" resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" From e793c4609770875f7a6f16cafa2ed5f8501a52fb Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Tue, 17 Nov 2020 13:25:57 +0000 Subject: [PATCH 09/15] Updates for screen refactor - finishing off getting everything over to the class based system. --- .../store/screenTemplates/newRowScreen.js | 125 +----- .../store/screenTemplates/rowDetailScreen.js | 360 ++++-------------- .../store/screenTemplates/rowListScreen.js | 254 +++++------- .../screenTemplates/utils/commonComponents.js | 125 +++++- packages/client/src/render/screenRouter.js | 2 +- .../src/utilities/security/accessLevels.js | 2 +- 6 files changed, 307 insertions(+), 561 deletions(-) diff --git a/packages/builder/src/builderStore/store/screenTemplates/newRowScreen.js b/packages/builder/src/builderStore/store/screenTemplates/newRowScreen.js index 58fb4445a2..90c7d2d3fd 100644 --- a/packages/builder/src/builderStore/store/screenTemplates/newRowScreen.js +++ b/packages/builder/src/builderStore/store/screenTemplates/newRowScreen.js @@ -1,8 +1,7 @@ import sanitizeUrl from "./utils/sanitizeUrl" -import { rowListUrl } from "./rowListScreen" import { Component } from "./utils/Component" import { Screen } from "./utils/Screen" -import { linkComponent } from "./utils/commonComponents" +import { makeBreadcrumbContainer, makeMainContainer, makeTitleContainer, makeSaveButton } from "./utils/commonComponents" export default function(tables) { return tables.map(table => { @@ -17,133 +16,25 @@ export default function(tables) { export const newRowUrl = table => sanitizeUrl(`/${table.name}/new`) export const NEW_ROW_TEMPLATE = "NEW_ROW_TEMPLATE" -function breadcrumbContainer(table) { - const link = linkComponent(table.name).instanceName("Back Link") - - const arrowText = new Component("@budibase/standard-components/text") - .type("none") - .normalStyle({ - "margin-right": "4px", - "margin-left": "4px", - }) - .text(">") - .instanceName("Arrow") - - const newText = new Component("@budibase/standard-components/text") - .type("none") - .normalStyle({ - color: "#000000", - }) - .text("New") - .instanceName("Identifier") - - return new Component("@budibase/standard-components/container") - .type("div") - .normalStyle({ - "font-size": "14px", - color: "#757575", - }) - .instanceName("Breadcrumbs") - .addChild(link) - .addChild(arrowText) - .addChild(newText) -} - -function titleContainer(table) { - const heading = new Component("@budibase/standard-components/heading") - .normalStyle({ - margin: "0px", - "margin-bottom": "0px", - "margin-right": "0px", - "margin-top": "0px", - "margin-left": "0px", - flex: "1 1 auto", - }) - .type("h3") - .instanceName("Title") - .text("New Row") - - const button = new Component("@budibase/standard-components/button") - .normalStyle({ - background: "#000000", - "border-width": "0", - "border-style": "None", - color: "#fff", - "font-family": "Inter", - "font-weight": "500", - "font-size": "14px", - "margin-left": "16px", - }) - .hoverStyle({ - background: "#4285f4", - }) - .text("Save") - .customProps({ - className: "", - disabled: false, - onClick: [ - { - parameters: { - contextPath: "data", - tableId: table._id, - }, - "##eventHandlerType": "Save Row", - }, - { - parameters: { - url: rowListUrl(table), - }, - "##eventHandlerType": "Navigate To", - }, - ], - }) - .instanceName("Save Button") - - return new Component("@budibase/standard-components/container") - .type("div") - .normalStyle({ - display: "flex", - "flex-direction": "row", - "justify-content": "space-between", - "align-items": "center", - "margin-top": "32px", - "margin-bottom": "32px", - }) - .instanceName("Title Container") - .addChild(heading) - .addChild(button) +function generateTitleContainer(table) { + return makeTitleContainer("New Row") + .addChild(makeSaveButton(table)) } const createScreen = table => { const dataform = new Component("@budibase/standard-components/dataformwide") .instanceName("Form") - const mainContainer = new Component("@budibase/standard-components/container") - .type("div") - .normalStyle({ - width: "700px", - padding: "0px", - background: "white", - "border-radius": "0.5rem", - "box-shadow": "0 1px 2px 0 rgba(0, 0, 0, 0.05)", - margin: "auto", - "margin-top": "20px", - "padding-top": "48px", - "padding-bottom": "48px", - "padding-right": "48px", - "padding-left": "48px", - "margin-bottom": "20px", - }) - .instanceName("Container") - .addChild(breadcrumbContainer(table)) - .addChild(titleContainer(table)) + const container = makeMainContainer() + .addChild(makeBreadcrumbContainer(table.name, "New")) + .addChild(generateTitleContainer(table)) .addChild(dataform) return new Screen().component("@budibase/standard-components/newrow") - .addChild(mainContainer) .table(table._id) .route(newRowUrl(table)) .instanceName(`${table.name} - New`) .name("") + .addChild(container) .json() } diff --git a/packages/builder/src/builderStore/store/screenTemplates/rowDetailScreen.js b/packages/builder/src/builderStore/store/screenTemplates/rowDetailScreen.js index 2ec985b90a..795ac09ea0 100644 --- a/packages/builder/src/builderStore/store/screenTemplates/rowDetailScreen.js +++ b/packages/builder/src/builderStore/store/screenTemplates/rowDetailScreen.js @@ -1,5 +1,8 @@ import sanitizeUrl from "./utils/sanitizeUrl" import { rowListUrl } from "./rowListScreen" +import { Screen } from "./utils/Screen" +import { Component } from "./utils/Component" +import { makeMainContainer, makeBreadcrumbContainer, makeTitleContainer, makeSaveButton } from "./utils/commonComponents" export default function(tables) { return tables.map(table => { @@ -17,291 +20,78 @@ export default function(tables) { export const ROW_DETAIL_TEMPLATE = "ROW_DETAIL_TEMPLATE" export const rowDetailUrl = table => sanitizeUrl(`/${table.name}/:id`) -const createScreen = (table, heading) => ({ - props: { - _id: "c683c4ca8ffc849c6bdd3b7d637fbbf3c", - _component: "@budibase/standard-components/rowdetail", - _styles: { - normal: {}, - hover: {}, - active: {}, - selected: {}, - }, - table: table._id, - _children: [ - { - _id: "ccad6cc135c7947a7ba9c631f655d6e0f", - _component: "@budibase/standard-components/container", - _styles: { - normal: { - width: "700px", - padding: "0px", - background: "white", - "border-radius": "0.5rem", - "box-shadow": "0 1px 2px 0 rgba(0, 0, 0, 0.05)", - margin: "auto", - "margin-top": "20px", - "padding-top": "48px", - "padding-bottom": "48px", - "padding-right": "48px", - "padding-left": "48px", - "margin-bottom": "20px", +function generateTitleContainer(table, title) { + // have to override style for this, its missing margin + const saveButton = makeSaveButton(table) + .normalStyle({ + background: "#000000", + "border-width": "0", + "border-style": "None", + color: "#fff", + "font-family": "Inter", + "font-weight": "500", + "font-size": "14px", + }) + + const deleteButton = new Component("@budibase/standard-components/button") + .normalStyle({ + background: "transparent", + "border-width": "0", + "border-style": "None", + color: "#9e9e9e", + "font-family": "Inter", + "font-weight": "500", + "font-size": "14px", + "margin-right": "8px", + "margin-left": "16px", + }) + .hoverStyle({ + background: "transparent", + color: "#4285f4", + }) + .text("Delete") + .customProps({ + className: "", + disabled: false, + onClick: [ + { + parameters: { + rowId: "{{ data._id }}", + revId: "{{ data._rev }}", + tableId: table._id, }, - hover: {}, - active: {}, - selected: {}, + "##eventHandlerType": "Delete Row", }, - _code: "", - className: "", - onLoad: [], - type: "div", - _instanceId: "inst_app_8fb_631af42f9dc94da2b5c48dc6c5124610", - _instanceName: "Container", - _children: [ - { - _id: "c6e91622ba7984f468f70bf4bf5120246", - _component: "@budibase/standard-components/container", - _styles: { - normal: { - "font-size": "14px", - color: "#757575", - }, - hover: {}, - active: {}, - selected: {}, - }, - _code: "", - className: "", - onLoad: [], - type: "div", - _instanceId: "inst_app_8fb_631af42f9dc94da2b5c48dc6c5124610", - _instanceName: "Breadcrumbs", - _children: [ - { - _id: "caa33353c252c4931b2a51b48a559a7fc", - _component: "@budibase/standard-components/link", - _styles: { - normal: { - color: "#757575", - "text-transform": "capitalize", - }, - hover: { - color: "#4285f4", - }, - active: {}, - selected: {}, - }, - _code: "", - url: `/${table.name.toLowerCase()}`, - openInNewTab: false, - text: table.name, - color: "", - hoverColor: "", - underline: false, - fontSize: "", - fontFamily: "initial", - _instanceId: "inst_app_8fb_631af42f9dc94da2b5c48dc6c5124610", - _instanceName: "Back Link", - _children: [], - }, - { - _id: "c6e218170201040e7a74e2c8304fe1860", - _component: "@budibase/standard-components/text", - _styles: { - normal: { - "margin-right": "4px", - "margin-left": "4px", - }, - hover: {}, - active: {}, - selected: {}, - }, - _code: "", - text: ">", - type: "none", - _instanceId: "inst_app_8fb_631af42f9dc94da2b5c48dc6c5124610", - _instanceName: "Arrow", - _children: [], - }, - { - _id: "c799da1fa3a84442e947cc9199518f64c", - _component: "@budibase/standard-components/text", - _styles: { - normal: { - color: "#000000", - "text-transform": "capitalize", - }, - hover: {}, - active: {}, - selected: {}, - }, - _code: "", - text: heading || "Edit", - type: "none", - _instanceId: "inst_app_8fb_631af42f9dc94da2b5c48dc6c5124610", - _instanceName: "Identifier", - _children: [], - }, - ], + { + parameters: { + url: rowListUrl(table), }, - { - _id: "cbd1637cd1e274287a3c28ef0bf235d08", - _component: "@budibase/standard-components/container", - _styles: { - normal: { - display: "flex", - "flex-direction": "row", - "justify-content": "space-between", - "align-items": "center", - "margin-top": "32px", - "margin-bottom": "32px", - }, - hover: {}, - active: {}, - selected: {}, - }, - _code: "", - className: "", - onLoad: [], - type: "div", - _instanceId: "inst_app_8fb_631af42f9dc94da2b5c48dc6c5124610", - _instanceName: "Title Container", - _children: [ - { - _id: "c98d3675d04114558bbf28661c5ccfb8e", - _component: "@budibase/standard-components/heading", - _styles: { - normal: { - margin: "0px", - "margin-bottom": "0px", - "margin-right": "0px", - "margin-top": "0px", - "margin-left": "0px", - flex: "1 1 auto", - "text-transform": "capitalize", - }, - hover: {}, - active: {}, - selected: {}, - }, - _code: "", - className: "", - text: heading || "Edit Row", - type: "h3", - _instanceName: "Title", - _children: [], - }, - { - _id: "c0a162cfb7d1c4bcfa8d24c290ccd1fd6", - _component: "@budibase/standard-components/button", - _styles: { - normal: { - background: "transparent", - "border-width": "0", - "border-style": "None", - color: "#9e9e9e", - "font-family": "Inter", - "font-weight": "500", - "font-size": "14px", - "margin-right": "8px", - "margin-left": "16px", - }, - hover: { - background: "transparent", - color: "#4285f4", - }, - active: {}, - selected: {}, - }, - _code: "", - text: "Delete", - className: "", - disabled: false, - onClick: [ - { - parameters: { - rowId: "{{ data._id }}", - revId: "{{ data._rev }}", - tableId: table._id, - }, - "##eventHandlerType": "Delete Row", - }, - { - parameters: { - url: rowListUrl(table), - }, - "##eventHandlerType": "Navigate To", - }, - ], - _instanceName: "Delete Button", - _children: [], - }, - { - _id: "cae402bd3c6a44618a8341bf7ab9ab086", - _component: "@budibase/standard-components/button", - _styles: { - normal: { - background: "#000000", - "border-width": "0", - "border-style": "None", - color: "#fff", - "font-family": "Inter", - "font-weight": "500", - "font-size": "14px", - }, - hover: { - background: "#4285f4", - }, - active: {}, - selected: {}, - }, - _code: "", - text: "Save", - className: "", - disabled: false, - onClick: [ - { - parameters: { - contextPath: "data", - tableId: table._id, - }, - "##eventHandlerType": "Save Row", - }, - { - parameters: { - url: rowListUrl(table), - }, - "##eventHandlerType": "Navigate To", - }, - ], - _instanceName: "Save Button", - _children: [], - }, - ], - }, - { - _id: "c5e6c98d7363640f9ad3a7d19c8c10f67", - _component: "@budibase/standard-components/dataformwide", - _styles: { - normal: {}, - hover: {}, - active: {}, - selected: {}, - }, - _code: "", - _instanceId: "inst_app_8fb_631af42f9dc94da2b5c48dc6c5124610", - _instanceName: "Form", - _children: [], - }, - ], - }, - ], - _instanceName: `${table.name} - Detail`, - _code: "", - }, - routing: { - route: rowDetailUrl(table), - accessLevelId: "", - }, - name: "", -}) + "##eventHandlerType": "Navigate To", + }, + ], + }) + .instanceName("Delete Button") + + return makeTitleContainer(title) + .addChild(deleteButton) + .addChild(saveButton) +} + +const createScreen = (table, heading) => { + const dataform = new Component("@budibase/standard-components/dataformwide") + .instanceName("Form") + + const container = makeMainContainer() + .addChild(makeBreadcrumbContainer(table.name, heading || "Edit")) + .addChild(generateTitleContainer(table, heading || "Edit Row")) + .addChild(dataform) + + return new Screen() + .component("@budibase/standard-components/rowdetail") + .table(table._id) + .instanceName(`${table.name} - Detail`) + .route(rowDetailUrl(table)) + .name("") + .addChild(container) + .json() +} diff --git a/packages/builder/src/builderStore/store/screenTemplates/rowListScreen.js b/packages/builder/src/builderStore/store/screenTemplates/rowListScreen.js index d9243b9b2f..a54ae961be 100644 --- a/packages/builder/src/builderStore/store/screenTemplates/rowListScreen.js +++ b/packages/builder/src/builderStore/store/screenTemplates/rowListScreen.js @@ -1,5 +1,7 @@ import sanitizeUrl from "./utils/sanitizeUrl" import { newRowUrl } from "./newRowScreen" +import { Screen } from "./utils/Screen" +import { Component } from "./utils/Component" export default function(tables) { return tables.map(table => { @@ -14,162 +16,102 @@ export default function(tables) { export const ROW_LIST_TEMPLATE = "ROW_LIST_TEMPLATE" export const rowListUrl = table => sanitizeUrl(`/${table.name}`) -const createScreen = table => ({ - props: { - _id: "c7365379815e4457dbe703a886c2da43b", - _component: "@budibase/standard-components/container", - _styles: { - normal: {}, - hover: {}, - active: {}, - selected: {}, - }, - type: "div", - _children: [ - { - _id: "cf51241fc063d4d87be032dd509fe0244", - _component: "@budibase/standard-components/container", - _styles: { - normal: { - background: "white", - "border-radius": "0.5rem", - "box-shadow": "0 1px 2px 0 rgba(0, 0, 0, 0.05)", - margin: "auto", - "margin-top": "20px", - "border-width": "2px", - "border-color": "rgba(0, 0, 0, 0.1)", - "border-style": "None", - "padding-top": "48px", - "padding-bottom": "48px", - "padding-right": "48px", - "padding-left": "48px", - "margin-bottom": "20px", +function generateTitleContainer(table) { + const newButton = new Component("@budibase/standard-components/button") + .normalStyle({ + background: "#000000", + "border-width": "0", + "border-style": "None", + color: "#fff", + "font-family": "Inter", + "font-weight": "500", + "font-size": "14px", + }) + .hoverStyle({ + background: "#4285f4", + }) + .text("Create New") + .customProps({ + className: "", + disabled: false, + onClick: [ + { + parameters: { + url: newRowUrl(table), }, - hover: {}, - active: {}, - selected: {}, + "##eventHandlerType": "Navigate To", }, - _code: "", - className: "", - onLoad: [], - type: "div", - _instanceId: "inst_app_8fb_631af42f9dc94da2b5c48dc6c5124610", - _instanceName: "Container", - _children: [ - { - _id: "c73294c301fd145aabe9bbbbd96a150ac", - _component: "@budibase/standard-components/container", - _styles: { - normal: { - display: "flex", - "flex-direction": "row", - "justify-content": "space-between", - "align-items": "center", - "margin-bottom": "32px", - }, - hover: {}, - active: {}, - selected: {}, - }, - _code: "", - className: "", - onLoad: [], - type: "div", - _instanceId: "inst_app_8fb_631af42f9dc94da2b5c48dc6c5124610", - _instanceName: "Title Container", - _children: [ - { - _id: "c2b77901df95a4d1ca7204c58300bc94b", - _component: "@budibase/standard-components/heading", - _styles: { - normal: { - margin: "0px", - flex: "1 1 auto", - "text-transform": "capitalize", - }, - hover: {}, - active: {}, - selected: {}, - }, - _code: "", - className: "", - text: table.name, - type: "h3", - _instanceName: "Title", - _children: [], - }, - { - _id: "c12a82d77baf24ca9922ea0af7cd4f723", - _component: "@budibase/standard-components/button", - _styles: { - normal: { - background: "#000000", - "border-width": "0", - "border-style": "None", - color: "#fff", - "font-family": "Inter", - "font-weight": "500", - "font-size": "14px", - }, - hover: { - background: "#4285f4", - }, - active: {}, - selected: {}, - }, - _code: "", - text: "Create New", - className: "", - disabled: false, - onClick: [ - { - parameters: { - url: newRowUrl(table), - }, - "##eventHandlerType": "Navigate To", - }, - ], - _instanceName: "New Button", - _children: [], - }, - ], - }, - { - _id: "ca686a2ed89c943e6bafb63fa66a3ead3", - _component: "@budibase/standard-components/datagrid", - _styles: { - normal: {}, - hover: {}, - active: {}, - selected: {}, - }, - _code: "", - datasource: { - label: table.name, - name: `all_${table._id}`, - tableId: table._id, - type: "table", - }, - editable: false, - theme: "alpine", - height: "540", - pagination: true, - _instanceId: "inst_app_8fb_631af42f9dc94da2b5c48dc6c5124610", - _instanceName: "Grid", - _children: [], - detailUrl: `${table.name.toLowerCase()}/:id`, - }, - ], + ], + }) + .instanceName("New Button") + + const heading = new Component("@budibase/standard-components/heading") + .normalStyle({ + margin: "0px", + flex: "1 1 auto", + "text-transform": "capitalize", + }) + .type("h3") + .instanceName("Title") + .text(table.name) + + return new Component("@budibase/standard-components/container") + .type("div") + .normalStyle({ + display: "flex", + "flex-direction": "row", + "justify-content": "space-between", + "align-items": "center", + "margin-bottom": "32px", + }) + .instanceName("Title Container") + .addChild(heading) + .addChild(newButton) +} + +const createScreen = table => { + const datagrid = new Component("@budibase/standard-components/datagrid") + .customProps({ + datasource: { + label: table.name, + name: `all_${table._id}`, + tableId: table._id, + type: "table", }, - ], - _instanceName: `${table.name} - List`, - _code: "", - className: "", - onLoad: [], - }, - routing: { - route: rowListUrl(table), - accessLevelId: "", - }, - name: "", -}) + editable: false, + theme: "alpine", + height: "540", + pagination: true, + detailUrl: `${table.name.toLowerCase()}/:id` + }) + .instanceName("Grid") + + const mainContainer = new Component("@budibase/standard-components/container") + .normalStyle({ + background: "white", + "border-radius": "0.5rem", + "box-shadow": "0 1px 2px 0 rgba(0, 0, 0, 0.05)", + margin: "auto", + "margin-top": "20px", + "border-width": "2px", + "border-color": "rgba(0, 0, 0, 0.1)", + "border-style": "None", + "padding-top": "48px", + "padding-bottom": "48px", + "padding-right": "48px", + "padding-left": "48px", + "margin-bottom": "20px", + }) + .type("div") + .instanceName("Container") + .addChild(generateTitleContainer(table)) + .addChild(datagrid) + + return new Screen().component("@budibase/standard-components/container") + .mainType("div") + .route(rowListUrl(table)) + .instanceName(`${table.name} - List`) + .name("") + .addChild(mainContainer) + .json() +} diff --git a/packages/builder/src/builderStore/store/screenTemplates/utils/commonComponents.js b/packages/builder/src/builderStore/store/screenTemplates/utils/commonComponents.js index eae2d7f526..9f5bd81d11 100644 --- a/packages/builder/src/builderStore/store/screenTemplates/utils/commonComponents.js +++ b/packages/builder/src/builderStore/store/screenTemplates/utils/commonComponents.js @@ -1,6 +1,7 @@ import { Component } from "./Component" +import { rowListUrl } from "../rowListScreen" -export function linkComponent(tableName) { +export function makeLinkComponent(tableName) { return new Component("@budibase/standard-components/link") .normalStyle({ color: "#757575", @@ -19,4 +20,126 @@ export function linkComponent(tableName) { fontSize: "", fontFamily: "initial", }) +} + +export function makeMainContainer() { + return new Component("@budibase/standard-components/container") + .type("div") + .normalStyle({ + width: "700px", + padding: "0px", + background: "white", + "border-radius": "0.5rem", + "box-shadow": "0 1px 2px 0 rgba(0, 0, 0, 0.05)", + margin: "auto", + "margin-top": "20px", + "padding-top": "48px", + "padding-bottom": "48px", + "padding-right": "48px", + "padding-left": "48px", + "margin-bottom": "20px", + }) + .instanceName("Container") +} + +export function makeBreadcrumbContainer(tableName, text, capitalise = false) { + const link = makeLinkComponent(tableName).instanceName("Back Link") + + const arrowText = new Component("@budibase/standard-components/text") + .type("none") + .normalStyle({ + "margin-right": "4px", + "margin-left": "4px", + }) + .text(">") + .instanceName("Arrow") + + const textStyling = { + color: "#000000", + } + if (capitalise) { + textStyling["text-transform"] = "capitalize" + } + const identifierText = new Component("@budibase/standard-components/text") + .type("none") + .normalStyle(textStyling) + .text(text) + .instanceName("Identifier") + + return new Component("@budibase/standard-components/container") + .type("div") + .normalStyle({ + "font-size": "14px", + color: "#757575", + }) + .instanceName("Breadcrumbs") + .addChild(link) + .addChild(arrowText) + .addChild(identifierText) +} + +export function makeSaveButton(table) { + return new Component("@budibase/standard-components/button") + .normalStyle({ + background: "#000000", + "border-width": "0", + "border-style": "None", + color: "#fff", + "font-family": "Inter", + "font-weight": "500", + "font-size": "14px", + "margin-left": "16px", + }) + .hoverStyle({ + background: "#4285f4", + }) + .text("Save") + .customProps({ + className: "", + disabled: false, + onClick: [ + { + parameters: { + contextPath: "data", + tableId: table._id, + }, + "##eventHandlerType": "Save Row", + }, + { + parameters: { + url: rowListUrl(table), + }, + "##eventHandlerType": "Navigate To", + }, + ], + }) + .instanceName("Save Button") +} + +export function makeTitleContainer(title) { + const heading = new Component("@budibase/standard-components/heading") + .normalStyle({ + margin: "0px", + "margin-bottom": "0px", + "margin-right": "0px", + "margin-top": "0px", + "margin-left": "0px", + flex: "1 1 auto", + }) + .type("h3") + .instanceName("Title") + .text(title) + + return new Component("@budibase/standard-components/container") + .type("div") + .normalStyle({ + display: "flex", + "flex-direction": "row", + "justify-content": "space-between", + "align-items": "center", + "margin-top": "32px", + "margin-bottom": "32px", + }) + .instanceName("Title Container") + .addChild(heading) } \ No newline at end of file diff --git a/packages/client/src/render/screenRouter.js b/packages/client/src/render/screenRouter.js index 7711b53acf..23bfa34944 100644 --- a/packages/client/src/render/screenRouter.js +++ b/packages/client/src/render/screenRouter.js @@ -43,7 +43,7 @@ export const screenRouter = ({ screens, onScreenSelected, window }) => { return sanitize(url) } - const routes = screens.map(s => makeRootedPath(s.route)) + const routes = screens.map(s => makeRootedPath(s.routing?.route)) let fallback = routes.findIndex(([p]) => p === makeRootedPath("*")) if (fallback < 0) fallback = 0 diff --git a/packages/server/src/utilities/security/accessLevels.js b/packages/server/src/utilities/security/accessLevels.js index e99b635f39..80cc652390 100644 --- a/packages/server/src/utilities/security/accessLevels.js +++ b/packages/server/src/utilities/security/accessLevels.js @@ -18,7 +18,7 @@ function AccessLevel(id, name, inherits = null) { exports.BUILTIN_LEVELS = { ADMIN: new AccessLevel(BUILTIN_IDS.ADMIN, "Admin", BUILTIN_IDS.POWER), - POWER: new AccessLevel(BUILTIN_IDS.POWER, "Admin", BUILTIN_IDS.BASIC), + POWER: new AccessLevel(BUILTIN_IDS.POWER, "Power", BUILTIN_IDS.BASIC), BASIC: new AccessLevel(BUILTIN_IDS.BASIC, "Basic", BUILTIN_IDS.ANON), ANON: new AccessLevel(BUILTIN_IDS.ANON, "Anonymous"), BUILDER: new AccessLevel(BUILTIN_IDS.BUILDER, "Builder"), From 3252b2d63037334a69f3488afce8c56118fae4e5 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Tue, 17 Nov 2020 18:12:21 +0000 Subject: [PATCH 10/15] Finishing up routing structure, now available on /api/routing. --- .../server/src/api/controllers/routing.js | 35 +++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/packages/server/src/api/controllers/routing.js b/packages/server/src/api/controllers/routing.js index 3be2e3fd41..bd5d991dfa 100644 --- a/packages/server/src/api/controllers/routing.js +++ b/packages/server/src/api/controllers/routing.js @@ -2,8 +2,38 @@ const { getRoutingInfo } = require("../../utilities/routing") const { AccessController } = require("../../utilities/security/accessLevels") async function getRoutingStructure(appId) { - let baseRouting = await getRoutingInfo(appId) - return baseRouting + const screenRoutes = await getRoutingInfo(appId) + const routing = {} + for (let screenRoute of screenRoutes) { + const fullpath = screenRoute.routing.route + // replace the first value with the home route + const subpaths = ["/"].concat(fullpath.split("/").splice(1)) + // special case for when it is simply the home route "/", this creates a weird scenario + if (subpaths[1] === "") { + subpaths.splice(1, 1) + } + const accessLevel = screenRoute.routing.accessLevelId + // iterate through the tree initially to flesh out all the required subpaths + let currentPath = routing, + nextSubpath = routing + for (let subpath of subpaths) { + if (!nextSubpath[subpath]) { + nextSubpath[subpath] = { + subpaths: {}, + } + } + currentPath = nextSubpath ? nextSubpath : currentPath[subpath] + nextSubpath = currentPath[subpath].subpaths + } + const correctPath = currentPath[subpaths[subpaths.length - 1]] + if (!correctPath.screens) { + correctPath.screens = {} + } + correctPath.screens[accessLevel] = screenRoute.id + correctPath.fullpath = fullpath + } + + return { routes: routing } } exports.fetch = async ctx => { @@ -14,4 +44,5 @@ exports.clientFetch = async ctx => { const routing = getRoutingStructure(ctx.appId) // use the access controller to pick which access level is applicable to this user const accessController = new AccessController(ctx.appId) + // TODO: iterate through the routes and pick which the user can access } From 63f7641c9e1360de28a4f5d2d1e548311672715f Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Tue, 17 Nov 2020 18:13:21 +0000 Subject: [PATCH 11/15] linting. --- .../store/screenTemplates/newRowScreen.js | 18 +++++++---- .../store/screenTemplates/rowDetailScreen.js | 31 +++++++++++-------- .../store/screenTemplates/rowListScreen.js | 5 +-- .../screenTemplates/utils/BaseStructure.js | 3 +- .../screenTemplates/utils/commonComponents.js | 2 +- .../modals/CreateTableModal.svelte | 5 ++- .../EventsEditor/StateBindingCascader.svelte | 4 ++- packages/server/src/api/index.js | 2 +- 8 files changed, 43 insertions(+), 27 deletions(-) diff --git a/packages/builder/src/builderStore/store/screenTemplates/newRowScreen.js b/packages/builder/src/builderStore/store/screenTemplates/newRowScreen.js index 90c7d2d3fd..1e699dad95 100644 --- a/packages/builder/src/builderStore/store/screenTemplates/newRowScreen.js +++ b/packages/builder/src/builderStore/store/screenTemplates/newRowScreen.js @@ -1,7 +1,12 @@ import sanitizeUrl from "./utils/sanitizeUrl" import { Component } from "./utils/Component" import { Screen } from "./utils/Screen" -import { makeBreadcrumbContainer, makeMainContainer, makeTitleContainer, makeSaveButton } from "./utils/commonComponents" +import { + makeBreadcrumbContainer, + makeMainContainer, + makeTitleContainer, + makeSaveButton, +} from "./utils/commonComponents" export default function(tables) { return tables.map(table => { @@ -17,20 +22,21 @@ export const newRowUrl = table => sanitizeUrl(`/${table.name}/new`) export const NEW_ROW_TEMPLATE = "NEW_ROW_TEMPLATE" function generateTitleContainer(table) { - return makeTitleContainer("New Row") - .addChild(makeSaveButton(table)) + return makeTitleContainer("New Row").addChild(makeSaveButton(table)) } const createScreen = table => { - const dataform = new Component("@budibase/standard-components/dataformwide") - .instanceName("Form") + const dataform = new Component( + "@budibase/standard-components/dataformwide" + ).instanceName("Form") const container = makeMainContainer() .addChild(makeBreadcrumbContainer(table.name, "New")) .addChild(generateTitleContainer(table)) .addChild(dataform) - return new Screen().component("@budibase/standard-components/newrow") + return new Screen() + .component("@budibase/standard-components/newrow") .table(table._id) .route(newRowUrl(table)) .instanceName(`${table.name} - New`) diff --git a/packages/builder/src/builderStore/store/screenTemplates/rowDetailScreen.js b/packages/builder/src/builderStore/store/screenTemplates/rowDetailScreen.js index 795ac09ea0..526f457f3e 100644 --- a/packages/builder/src/builderStore/store/screenTemplates/rowDetailScreen.js +++ b/packages/builder/src/builderStore/store/screenTemplates/rowDetailScreen.js @@ -2,7 +2,12 @@ import sanitizeUrl from "./utils/sanitizeUrl" import { rowListUrl } from "./rowListScreen" import { Screen } from "./utils/Screen" import { Component } from "./utils/Component" -import { makeMainContainer, makeBreadcrumbContainer, makeTitleContainer, makeSaveButton } from "./utils/commonComponents" +import { + makeMainContainer, + makeBreadcrumbContainer, + makeTitleContainer, + makeSaveButton, +} from "./utils/commonComponents" export default function(tables) { return tables.map(table => { @@ -22,16 +27,15 @@ export const rowDetailUrl = table => sanitizeUrl(`/${table.name}/:id`) function generateTitleContainer(table, title) { // have to override style for this, its missing margin - const saveButton = makeSaveButton(table) - .normalStyle({ - background: "#000000", - "border-width": "0", - "border-style": "None", - color: "#fff", - "font-family": "Inter", - "font-weight": "500", - "font-size": "14px", - }) + const saveButton = makeSaveButton(table).normalStyle({ + background: "#000000", + "border-width": "0", + "border-style": "None", + color: "#fff", + "font-family": "Inter", + "font-weight": "500", + "font-size": "14px", + }) const deleteButton = new Component("@budibase/standard-components/button") .normalStyle({ @@ -78,8 +82,9 @@ function generateTitleContainer(table, title) { } const createScreen = (table, heading) => { - const dataform = new Component("@budibase/standard-components/dataformwide") - .instanceName("Form") + const dataform = new Component( + "@budibase/standard-components/dataformwide" + ).instanceName("Form") const container = makeMainContainer() .addChild(makeBreadcrumbContainer(table.name, heading || "Edit")) diff --git a/packages/builder/src/builderStore/store/screenTemplates/rowListScreen.js b/packages/builder/src/builderStore/store/screenTemplates/rowListScreen.js index a54ae961be..54a066af9c 100644 --- a/packages/builder/src/builderStore/store/screenTemplates/rowListScreen.js +++ b/packages/builder/src/builderStore/store/screenTemplates/rowListScreen.js @@ -82,7 +82,7 @@ const createScreen = table => { theme: "alpine", height: "540", pagination: true, - detailUrl: `${table.name.toLowerCase()}/:id` + detailUrl: `${table.name.toLowerCase()}/:id`, }) .instanceName("Grid") @@ -107,7 +107,8 @@ const createScreen = table => { .addChild(generateTitleContainer(table)) .addChild(datagrid) - return new Screen().component("@budibase/standard-components/container") + return new Screen() + .component("@budibase/standard-components/container") .mainType("div") .route(rowListUrl(table)) .instanceName(`${table.name} - List`) diff --git a/packages/builder/src/builderStore/store/screenTemplates/utils/BaseStructure.js b/packages/builder/src/builderStore/store/screenTemplates/utils/BaseStructure.js index 7b2bb8e927..71daca9d1b 100644 --- a/packages/builder/src/builderStore/store/screenTemplates/utils/BaseStructure.js +++ b/packages/builder/src/builderStore/store/screenTemplates/utils/BaseStructure.js @@ -4,8 +4,7 @@ export class BaseStructure { constructor(isScreen) { this._isScreen = isScreen this._children = [] - this._json = { - } + this._json = {} } addChild(child) { diff --git a/packages/builder/src/builderStore/store/screenTemplates/utils/commonComponents.js b/packages/builder/src/builderStore/store/screenTemplates/utils/commonComponents.js index 9f5bd81d11..89e08cecdb 100644 --- a/packages/builder/src/builderStore/store/screenTemplates/utils/commonComponents.js +++ b/packages/builder/src/builderStore/store/screenTemplates/utils/commonComponents.js @@ -142,4 +142,4 @@ export function makeTitleContainer(title) { }) .instanceName("Title Container") .addChild(heading) -} \ No newline at end of file +} diff --git a/packages/builder/src/components/backend/TableNavigator/modals/CreateTableModal.svelte b/packages/builder/src/components/backend/TableNavigator/modals/CreateTableModal.svelte index f73810c340..f064ff923c 100644 --- a/packages/builder/src/components/backend/TableNavigator/modals/CreateTableModal.svelte +++ b/packages/builder/src/components/backend/TableNavigator/modals/CreateTableModal.svelte @@ -62,7 +62,10 @@ const listScreen = screens.find(screen => screen.props._instanceName.endsWith("List") ) - await store.actions.components.links.save(listScreen.routing.route, table.name) + await store.actions.components.links.save( + listScreen.routing.route, + table.name + ) // Navigate to new table $goto(`./table/${table._id}`) diff --git a/packages/builder/src/components/userInterface/EventsEditor/StateBindingCascader.svelte b/packages/builder/src/components/userInterface/EventsEditor/StateBindingCascader.svelte index 3de945adc5..3475a14eed 100644 --- a/packages/builder/src/components/userInterface/EventsEditor/StateBindingCascader.svelte +++ b/packages/builder/src/components/userInterface/EventsEditor/StateBindingCascader.svelte @@ -25,7 +25,9 @@ + {/each} {:else} diff --git a/packages/server/src/api/index.js b/packages/server/src/api/index.js index 500ca50ff2..c76eb82643 100644 --- a/packages/server/src/api/index.js +++ b/packages/server/src/api/index.js @@ -4,7 +4,7 @@ const compress = require("koa-compress") const zlib = require("zlib") const { budibaseAppsDir } = require("../utilities/budibaseDir") const { isDev } = require("../utilities") -const {mainRoutes, authRoutes, staticRoutes} = require("./routes") +const { mainRoutes, authRoutes, staticRoutes } = require("./routes") const router = new Router() const env = require("../environment") From 6a50b1057d283dd961877698f9a06d14af44ea2c Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Wed, 18 Nov 2020 15:12:42 +0000 Subject: [PATCH 12/15] Fixing some bugs with previous commit and updating to add the functionality of the api/routing/client. --- .../src/builderStore/store/frontend.js | 2 +- .../server/src/api/controllers/accesslevel.js | 4 +- .../server/src/api/controllers/routing.js | 74 ++++++++++++- packages/server/src/api/routes/routing.js | 2 +- .../src/api/routes/tests/couchTestUtils.js | 2 +- .../server/src/middleware/authenticated.js | 31 +----- .../src/utilities/security/accessLevels.js | 100 ++++++++++++------ 7 files changed, 144 insertions(+), 71 deletions(-) diff --git a/packages/builder/src/builderStore/store/frontend.js b/packages/builder/src/builderStore/store/frontend.js index bac3ba84ba..06e9428fa8 100644 --- a/packages/builder/src/builderStore/store/frontend.js +++ b/packages/builder/src/builderStore/store/frontend.js @@ -50,7 +50,7 @@ export const getFrontendStore = () => { return state }) const screens = await api.get("/api/screens").then(r => r.json()) - const routing = await api.get("/api/routing").then(r => r.json()) + const routing = await api.get("/api/routing/client").then(r => r.json()) const mainScreens = screens.filter(screen => screen._id.includes(pkg.pages.main._id) diff --git a/packages/server/src/api/controllers/accesslevel.js b/packages/server/src/api/controllers/accesslevel.js index 22bd1467fb..b2985e2953 100644 --- a/packages/server/src/api/controllers/accesslevel.js +++ b/packages/server/src/api/controllers/accesslevel.js @@ -2,6 +2,7 @@ const CouchDB = require("../../db") const { BUILTIN_LEVELS, AccessLevel, + getAccessLevel, } = require("../../utilities/security/accessLevels") const { generateAccessLevelID, @@ -22,8 +23,7 @@ exports.fetch = async function(ctx) { } exports.find = async function(ctx) { - const db = new CouchDB(ctx.user.appId) - ctx.body = await db.get(ctx.params.levelId) + ctx.body = await getAccessLevel(ctx.user.appId, ctx.params.levelId) } exports.save = async function(ctx) { diff --git a/packages/server/src/api/controllers/routing.js b/packages/server/src/api/controllers/routing.js index bd5d991dfa..763683185a 100644 --- a/packages/server/src/api/controllers/routing.js +++ b/packages/server/src/api/controllers/routing.js @@ -1,6 +1,15 @@ const { getRoutingInfo } = require("../../utilities/routing") -const { AccessController } = require("../../utilities/security/accessLevels") +const { + getUserAccessLevelHierarchy, + BUILTIN_LEVEL_IDS, +} = require("../../utilities/security/accessLevels") +/** + * Gets the full routing structure by querying the routing view and processing the result into the tree. + * @param {string} appId The application to produce the routing structure for. + * @returns {Promise} The routing structure, this is the full structure designed for use in the builder, + * if the client routing is required then the updateRoutingStructureForUserLevel should be used. + */ async function getRoutingStructure(appId) { const screenRoutes = await getRoutingInfo(appId) const routing = {} @@ -36,13 +45,68 @@ async function getRoutingStructure(appId) { return { routes: routing } } +/** + * A function for recursing through the routing structure and adjusting it to match the user's access level + * @param {object} path The routing path, retrieved from the getRoutingStructure function, when this recurses it will + * call with this parameter updated to the various subpaths. + * @param {string[]} accessLevelIds The full list of access level IDs, this has to be passed in as otherwise we would + * need to make this an async function purely for the first call, adds confusion to the recursion. + * @returns {object} The routing structure after it has been updated. + */ +function updateRoutingStructureForUserLevel(path, accessLevelIds) { + for (let routeKey of Object.keys(path)) { + const pathStructure = path[routeKey] + if (pathStructure.subpaths) { + pathStructure.subpaths = updateRoutingStructureForUserLevel( + pathStructure.subpaths, + accessLevelIds + ) + } + if (pathStructure.screens) { + const accessLevelOptions = Object.keys(pathStructure.screens) + // starts with highest level and works down through inheritance + let found = false + // special case for when the screen has no access control + if (accessLevelOptions.length === 1 && !accessLevelOptions[0]) { + pathStructure.screenId = pathStructure.screens[accessLevelOptions[0]] + pathStructure.accessLevelId = BUILTIN_LEVEL_IDS.BASIC + found = true + } else { + for (let levelId of accessLevelIds) { + if (accessLevelOptions.indexOf(levelId) !== -1) { + pathStructure.screenId = pathStructure.screens[levelId] + pathStructure.accessLevelId = levelId + found = true + break + } + } + } + // remove the screen options now that we've processed it + delete pathStructure.screens + // if no option was found then remove the route, user can't access it + if (!found) { + delete path[routeKey] + } + } + } + return path +} + exports.fetch = async ctx => { ctx.body = await getRoutingStructure(ctx.appId) } exports.clientFetch = async ctx => { - const routing = getRoutingStructure(ctx.appId) - // use the access controller to pick which access level is applicable to this user - const accessController = new AccessController(ctx.appId) - // TODO: iterate through the routes and pick which the user can access + const routing = await getRoutingStructure(ctx.appId) + const accessLevelId = ctx.user.accessLevel._id + // builder is a special case, always return the full routing structure + if (accessLevelId === BUILTIN_LEVEL_IDS.BUILDER) { + ctx.body = routing + return + } + const accessLevelIds = await getUserAccessLevelHierarchy( + ctx.appId, + accessLevelId + ) + ctx.body = updateRoutingStructureForUserLevel(routing.routes, accessLevelIds) } diff --git a/packages/server/src/api/routes/routing.js b/packages/server/src/api/routes/routing.js index 60f84de781..32eb14d390 100644 --- a/packages/server/src/api/routes/routing.js +++ b/packages/server/src/api/routes/routing.js @@ -7,7 +7,7 @@ const router = Router() // gets the full structure, not just the correct screen ID for your access level router - .get("/api/routing", authorized(BUILDER), controller.fetch) .get("/api/routing/client", controller.clientFetch) + .get("/api/routing", authorized(BUILDER), controller.fetch) module.exports = router diff --git a/packages/server/src/api/routes/tests/couchTestUtils.js b/packages/server/src/api/routes/tests/couchTestUtils.js index 0e137f1a44..85f4c44a62 100644 --- a/packages/server/src/api/routes/tests/couchTestUtils.js +++ b/packages/server/src/api/routes/tests/couchTestUtils.js @@ -188,7 +188,7 @@ const createUserWithPermissions = async ( const anonUser = { userId: "ANON", - accessLevelId: BUILTIN_LEVEL_IDS.ANON, + accessLevelId: BUILTIN_LEVEL_IDS.PUBLIC, appId: appId, version: packageJson.version, } diff --git a/packages/server/src/middleware/authenticated.js b/packages/server/src/middleware/authenticated.js index c0fbbdb86c..b30e22f0e1 100644 --- a/packages/server/src/middleware/authenticated.js +++ b/packages/server/src/middleware/authenticated.js @@ -1,7 +1,6 @@ const jwt = require("jsonwebtoken") const STATUS_CODES = require("../utilities/statusCodes") -const accessLevelController = require("../api/controllers/accesslevel") -const { BUILTIN_LEVEL_ID_ARRAY } = require("../utilities/security/accessLevels") +const { getAccessLevel } = require("../utilities/security/accessLevels") const env = require("../environment") const { AuthTypes } = require("../constants") const { getAppId, getCookieName, setCookie } = require("../utilities") @@ -60,31 +59,3 @@ module.exports = async (ctx, next) => { await next() } - -/** - * Return the full access level object either from constants - * or the database based on the access level ID passed. - * - * @param {*} appId - appId of the user - * @param {*} accessLevelId - the id of the users access level - */ -const getAccessLevel = async (appId, accessLevelId) => { - if (BUILTIN_LEVEL_ID_ARRAY.indexOf(accessLevelId) !== -1) { - return { - _id: accessLevelId, - name: accessLevelId, - permissions: [], - } - } - - const findAccessContext = { - params: { - levelId: accessLevelId, - }, - user: { - appId, - }, - } - await accessLevelController.find(findAccessContext) - return findAccessContext.body -} diff --git a/packages/server/src/utilities/security/accessLevels.js b/packages/server/src/utilities/security/accessLevels.js index 80cc652390..578cd1e803 100644 --- a/packages/server/src/utilities/security/accessLevels.js +++ b/packages/server/src/utilities/security/accessLevels.js @@ -1,14 +1,15 @@ const CouchDB = require("../../db") +const { cloneDeep } = require("lodash/fp") const BUILTIN_IDS = { ADMIN: "ADMIN", POWER: "POWER_USER", BASIC: "BASIC", - ANON: "ANON", + PUBLIC: "PUBLIC", BUILDER: "BUILDER", } -function AccessLevel(id, name, inherits = null) { +function AccessLevel(id, name, inherits) { this._id = id this.name = name if (inherits) { @@ -19,8 +20,8 @@ function AccessLevel(id, name, inherits = null) { exports.BUILTIN_LEVELS = { ADMIN: new AccessLevel(BUILTIN_IDS.ADMIN, "Admin", BUILTIN_IDS.POWER), POWER: new AccessLevel(BUILTIN_IDS.POWER, "Power", BUILTIN_IDS.BASIC), - BASIC: new AccessLevel(BUILTIN_IDS.BASIC, "Basic", BUILTIN_IDS.ANON), - ANON: new AccessLevel(BUILTIN_IDS.ANON, "Anonymous"), + BASIC: new AccessLevel(BUILTIN_IDS.BASIC, "Basic", BUILTIN_IDS.PUBLIC), + ANON: new AccessLevel(BUILTIN_IDS.PUBLIC, "Public"), BUILDER: new AccessLevel(BUILTIN_IDS.BUILDER, "Builder"), } @@ -36,27 +37,64 @@ function isBuiltin(accessLevel) { return exports.BUILTIN_LEVEL_ID_ARRAY.indexOf(accessLevel) !== -1 } +/** + * Gets the access level object, this is mainly useful for two purposes, to check if the level exists and + * to check if the access level inherits any others. + * @param {string} appId The app in which to look for the access level. + * @param {string|null} accessLevelId The level ID to lookup. + * @returns {Promise} The access level object, which may contain an "inherits" property. + */ +exports.getAccessLevel = async (appId, accessLevelId) => { + if (!accessLevelId) { + return null + } + let accessLevel + if (isBuiltin(accessLevelId)) { + accessLevel = cloneDeep( + Object.values(exports.BUILTIN_LEVELS).find( + level => level._id === accessLevelId + ) + ) + } else { + const db = new CouchDB(appId) + accessLevel = await db.get(accessLevelId) + } + return accessLevel +} + +/** + * Returns an ordered array of the user's inherited access level IDs, this can be used + * to determine if a user can access something that requires a specific access level. + * @param {string} appId The ID of the application from which access levels should be obtained. + * @param {string} userAccessLevelId The user's access level, this can be found in their access token. + * @returns {Promise} returns an ordered array of the access levels, with the first being their + * highest level of access and the last being the lowest level. + */ +exports.getUserAccessLevelHierarchy = async (appId, userAccessLevelId) => { + // special case, if they don't have a level then they are a public user + if (!userAccessLevelId) { + return [BUILTIN_IDS.PUBLIC] + } + let accessLevelIds = [userAccessLevelId] + let userAccess = await exports.getAccessLevel(appId, userAccessLevelId) + // check if inherited makes it possible + while ( + userAccess && + userAccess.inherits && + accessLevelIds.indexOf(userAccess.inherits) === -1 + ) { + accessLevelIds.push(userAccess.inherits) + // go to get the inherited incase it inherits anything + userAccess = await exports.getAccessLevel(appId, userAccess.inherits) + } + // add the user's actual level at the end (not at start as that stops iteration + return accessLevelIds +} + class AccessController { constructor(appId) { this.appId = appId - this.accessLevels = {} - } - - async getAccessLevel(accessLevelId) { - if (this.accessLevels[accessLevelId]) { - return this.accessLevels[accessLevelId] - } - let accessLevel - if (isBuiltin(accessLevelId)) { - accessLevel = Object.values(exports.BUILTIN_LEVELS).find( - level => level._id === accessLevelId - ) - } else { - const db = new CouchDB(this.appId) - accessLevel = await db.get(accessLevelId) - } - this.accessLevels[accessLevelId] = accessLevel - return accessLevel + this.userHierarchies = {} } async hasAccess(tryingAccessLevelId, userAccessLevelId) { @@ -70,16 +108,16 @@ class AccessController { ) { return true } - let userAccess = await this.getAccessLevel(userAccessLevelId) - // check if inherited makes it possible - while (userAccess.inherits) { - if (tryingAccessLevelId === userAccess.inherits) { - return true - } - // go to get the inherited incase it inherits anything - userAccess = await this.getAccessLevel(userAccess.inherits) + let accessLevelIds = this.userHierarchies[userAccessLevelId] + if (!accessLevelIds) { + accessLevelIds = await exports.getUserAccessLevelHierarchy( + this.appId, + userAccessLevelId + ) + this.userHierarchies[userAccessLevelId] = userAccessLevelId } - return false + + return accessLevelIds.indexOf(tryingAccessLevelId) !== -1 } async checkScreensAccess(screens, userAccessLevelId) { From 82feb6d740162da5764cc7d7722681abc80a4d20 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Wed, 18 Nov 2020 15:13:25 +0000 Subject: [PATCH 13/15] Changing back to builder getting the normal routing structure for builder. --- packages/builder/src/builderStore/store/frontend.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/builder/src/builderStore/store/frontend.js b/packages/builder/src/builderStore/store/frontend.js index 06e9428fa8..bac3ba84ba 100644 --- a/packages/builder/src/builderStore/store/frontend.js +++ b/packages/builder/src/builderStore/store/frontend.js @@ -50,7 +50,7 @@ export const getFrontendStore = () => { return state }) const screens = await api.get("/api/screens").then(r => r.json()) - const routing = await api.get("/api/routing/client").then(r => r.json()) + const routing = await api.get("/api/routing").then(r => r.json()) const mainScreens = screens.filter(screen => screen._id.includes(pkg.pages.main._id) From 3cda7ca4899a1e9a96563c3cca607e63c38d46cd Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Wed, 18 Nov 2020 18:24:12 +0000 Subject: [PATCH 14/15] Creating a function for the client to be able to pull in client definition from API. --- .../server/src/api/controllers/application.js | 51 +++++++++++++++---- packages/server/src/api/routes/application.js | 1 + 2 files changed, 43 insertions(+), 9 deletions(-) diff --git a/packages/server/src/api/controllers/application.js b/packages/server/src/api/controllers/application.js index dd44bda69a..512299c5c8 100644 --- a/packages/server/src/api/controllers/application.js +++ b/packages/server/src/api/controllers/application.js @@ -15,9 +15,11 @@ const { DocumentTypes, SEPARATOR, getPageParams, + getScreenParams, generatePageID, generateScreenID, } = require("../../db/utils") +const { BUILTIN_LEVEL_IDS } = require("../../utilities/security/accessLevels") const { downloadExtractComponentLibraries, } = require("../../utilities/createAppPackage") @@ -27,6 +29,20 @@ const { cloneDeep } = require("lodash/fp") const APP_PREFIX = DocumentTypes.APP + SEPARATOR +// utility function, need to do away with this +async function getMainAndUnauthPage(db) { + let pages = await db.allDocs( + getPageParams(null, { + include_docs: true, + }) + ) + pages = pages.rows.map(row => row.doc) + + const mainPage = pages.find(page => page.name === PageTypes.MAIN) + const unauthPage = pages.find(page => page.name === PageTypes.UNAUTHENTICATED) + return { mainPage, unauthPage } +} + async function createInstance(template) { const appId = generateAppID() @@ -67,19 +83,36 @@ exports.fetch = async function(ctx) { } } +exports.fetchAppDefinition = async function(ctx) { + const db = new CouchDB(ctx.params.appId) + // TODO: need to get rid of pages here, they shouldn't be needed anymore + const { mainPage, unauthPage } = await getMainAndUnauthPage(db) + const userAccessLevelId = + !ctx.user.accessLevel || !ctx.user.accessLevel._id + ? BUILTIN_LEVEL_IDS.PUBLIC + : ctx.user.accessLevel._id + const correctPage = + userAccessLevelId === BUILTIN_LEVEL_IDS.PUBLIC ? unauthPage : mainPage + const screens = ( + await db.allDocs( + getScreenParams(correctPage._id, { + include_docs: true, + }) + ) + ).rows.map(row => row.doc) + // TODO: need to handle access control here, limit screens to user access level + ctx.body = { + page: correctPage, + screens: screens, + libraries: ["@budibase/standard-components"], + } +} + exports.fetchAppPackage = async function(ctx) { const db = new CouchDB(ctx.params.appId) const application = await db.get(ctx.params.appId) - let pages = await db.allDocs( - getPageParams(null, { - include_docs: true, - }) - ) - pages = pages.rows.map(row => row.doc) - - const mainPage = pages.find(page => page.name === PageTypes.MAIN) - const unauthPage = pages.find(page => page.name === PageTypes.UNAUTHENTICATED) + const { mainPage, unauthPage } = await getMainAndUnauthPage(db) ctx.body = { application, pages: { diff --git a/packages/server/src/api/routes/application.js b/packages/server/src/api/routes/application.js index e3b4ddf6cf..3ee5da058c 100644 --- a/packages/server/src/api/routes/application.js +++ b/packages/server/src/api/routes/application.js @@ -6,6 +6,7 @@ const { BUILDER } = require("../../utilities/security/permissions") const router = Router() router + .get("/api/:appId/definition", controller.fetchAppDefinition) .get("/api/applications", authorized(BUILDER), controller.fetch) .get( "/api/:appId/appPackage", From 790d5718afb39cf72497bb2a661ae9ee87760353 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Thu, 19 Nov 2020 13:39:17 +0000 Subject: [PATCH 15/15] Updates to routing to follow the new style of single tree with all the subpaths nested as the full path. --- .../server/src/api/controllers/routing.js | 139 ++++++++---------- 1 file changed, 64 insertions(+), 75 deletions(-) diff --git a/packages/server/src/api/controllers/routing.js b/packages/server/src/api/controllers/routing.js index 763683185a..8d96863593 100644 --- a/packages/server/src/api/controllers/routing.js +++ b/packages/server/src/api/controllers/routing.js @@ -4,6 +4,39 @@ const { BUILTIN_LEVEL_IDS, } = require("../../utilities/security/accessLevels") +const URL_SEPARATOR = "/" + +function Routing() { + this.json = {} +} + +Routing.prototype.getTopLevel = function(fullpath) { + if (fullpath.charAt(0) !== URL_SEPARATOR) { + fullpath = URL_SEPARATOR + fullpath + } + // replace the first value with the home route + return URL_SEPARATOR + fullpath.split(URL_SEPARATOR)[1] +} + +Routing.prototype.getScreensProp = function(fullpath) { + const topLevel = this.getTopLevel(fullpath) + if (!this.json[topLevel]) { + this.json[topLevel] = { + subpaths: {}, + } + } + if (!this.json[topLevel].subpaths[fullpath]) { + this.json[topLevel].subpaths[fullpath] = { + screens: {}, + } + } + return this.json[topLevel].subpaths[fullpath].screens +} + +Routing.prototype.addScreenId = function(fullpath, accessLevel, screenId) { + this.getScreensProp(fullpath)[accessLevel] = screenId +} + /** * Gets the full routing structure by querying the routing view and processing the result into the tree. * @param {string} appId The application to produce the routing structure for. @@ -12,84 +45,15 @@ const { */ async function getRoutingStructure(appId) { const screenRoutes = await getRoutingInfo(appId) - const routing = {} + const routing = new Routing() + for (let screenRoute of screenRoutes) { - const fullpath = screenRoute.routing.route - // replace the first value with the home route - const subpaths = ["/"].concat(fullpath.split("/").splice(1)) - // special case for when it is simply the home route "/", this creates a weird scenario - if (subpaths[1] === "") { - subpaths.splice(1, 1) - } + let fullpath = screenRoute.routing.route const accessLevel = screenRoute.routing.accessLevelId - // iterate through the tree initially to flesh out all the required subpaths - let currentPath = routing, - nextSubpath = routing - for (let subpath of subpaths) { - if (!nextSubpath[subpath]) { - nextSubpath[subpath] = { - subpaths: {}, - } - } - currentPath = nextSubpath ? nextSubpath : currentPath[subpath] - nextSubpath = currentPath[subpath].subpaths - } - const correctPath = currentPath[subpaths[subpaths.length - 1]] - if (!correctPath.screens) { - correctPath.screens = {} - } - correctPath.screens[accessLevel] = screenRoute.id - correctPath.fullpath = fullpath + routing.addScreenId(fullpath, accessLevel, screenRoute.id) } - return { routes: routing } -} - -/** - * A function for recursing through the routing structure and adjusting it to match the user's access level - * @param {object} path The routing path, retrieved from the getRoutingStructure function, when this recurses it will - * call with this parameter updated to the various subpaths. - * @param {string[]} accessLevelIds The full list of access level IDs, this has to be passed in as otherwise we would - * need to make this an async function purely for the first call, adds confusion to the recursion. - * @returns {object} The routing structure after it has been updated. - */ -function updateRoutingStructureForUserLevel(path, accessLevelIds) { - for (let routeKey of Object.keys(path)) { - const pathStructure = path[routeKey] - if (pathStructure.subpaths) { - pathStructure.subpaths = updateRoutingStructureForUserLevel( - pathStructure.subpaths, - accessLevelIds - ) - } - if (pathStructure.screens) { - const accessLevelOptions = Object.keys(pathStructure.screens) - // starts with highest level and works down through inheritance - let found = false - // special case for when the screen has no access control - if (accessLevelOptions.length === 1 && !accessLevelOptions[0]) { - pathStructure.screenId = pathStructure.screens[accessLevelOptions[0]] - pathStructure.accessLevelId = BUILTIN_LEVEL_IDS.BASIC - found = true - } else { - for (let levelId of accessLevelIds) { - if (accessLevelOptions.indexOf(levelId) !== -1) { - pathStructure.screenId = pathStructure.screens[levelId] - pathStructure.accessLevelId = levelId - found = true - break - } - } - } - // remove the screen options now that we've processed it - delete pathStructure.screens - // if no option was found then remove the route, user can't access it - if (!found) { - delete path[routeKey] - } - } - } - return path + return { routes: routing.json } } exports.fetch = async ctx => { @@ -108,5 +72,30 @@ exports.clientFetch = async ctx => { ctx.appId, accessLevelId ) - ctx.body = updateRoutingStructureForUserLevel(routing.routes, accessLevelIds) + for (let topLevel of Object.values(routing.routes)) { + for (let subpathKey of Object.keys(topLevel.subpaths)) { + let found = false + const subpath = topLevel.subpaths[subpathKey] + const accessLevelOptions = Object.keys(subpath.screens) + if (accessLevelOptions.length === 1 && !accessLevelOptions[0]) { + subpath.screenId = subpath.screens[accessLevelOptions[0]] + subpath.accessLevelId = BUILTIN_LEVEL_IDS.BASIC + found = true + } else { + for (let levelId of accessLevelIds) { + if (accessLevelOptions.indexOf(levelId) !== -1) { + subpath.screenId = subpath.screens[levelId] + subpath.accessLevelId = levelId + found = true + break + } + } + } + delete subpath.screens + if (!found) { + delete topLevel.subpaths[subpathKey] + } + } + } + ctx.body = routing }