diff --git a/packages/builder/package.json b/packages/builder/package.json
index 68ff7f79d0..4254e3c7f6 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 31b8dcff22..2c1def6efb 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/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/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/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 @@
{: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 @@
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/builder/yarn.lock b/packages/builder/yarn.lock
index fecd51a497..7c00685aa6 100644
--- a/packages/builder/yarn.lock
+++ b/packages/builder/yarn.lock
@@ -719,10 +719,10 @@
svelte-flatpickr "^2.4.0"
svelte-portal "^1.0.0"
-"@budibase/client@^0.3.7":
- version "0.3.7"
- resolved "https://registry.yarnpkg.com/@budibase/client/-/client-0.3.7.tgz#8ed2d40d91ba3788a69ee5db5078f757adb4187f"
- integrity sha512-EgpHfw/WOUYeCG4cILDbaN2WFBDSPS698Z+So7FP5l+4E1fvmqtpXVKJYsviwYEx8AKKYyU3nuDi0l6xzb5Flg==
+"@budibase/client@^0.3.8":
+ 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"
@@ -6412,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"
diff --git a/packages/server/src/api/controllers/accesslevel.js b/packages/server/src/api/controllers/accesslevel.js
index 143c633e42..22bd1467fb 100644
--- a/packages/server/src/api/controllers/accesslevel.js
+++ b/packages/server/src/api/controllers/accesslevel.js
@@ -1,10 +1,8 @@
const CouchDB = require("../../db")
const {
- generateAdminPermissions,
- generatePowerUserPermissions,
- POWERUSER_LEVEL_ID,
- ADMIN_LEVEL_ID,
-} = require("../../utilities/accessLevels")
+ BUILTIN_LEVELS,
+ AccessLevel,
+} = require("../../utilities/security/accessLevels")
const {
generateAccessLevelID,
getAccessLevelParams,
@@ -19,19 +17,7 @@ exports.fetch = async function(ctx) {
)
const customAccessLevels = body.rows.map(row => row.doc)
- const staticAccessLevels = [
- {
- _id: ADMIN_LEVEL_ID,
- name: "Admin",
- permissions: await generateAdminPermissions(ctx.user.appId),
- },
- {
- _id: POWERUSER_LEVEL_ID,
- name: "Power User",
- permissions: await generatePowerUserPermissions(ctx.user.appId),
- },
- ]
-
+ const staticAccessLevels = [BUILTIN_LEVELS.ADMIN, BUILTIN_LEVELS.POWER]
ctx.body = [...staticAccessLevels, ...customAccessLevels]
}
@@ -40,64 +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(
- p =>
- !removedPermissions.some(
- rem => rem.name === p.name && rem.itemId === p.itemId
- )
- )
- }
-
- if (addedPermissions) {
- level.permissions = [
- ...level.permissions.filter(
- p =>
- !addedPermissions.some(
- add => add.name === p.name && add.itemId === p.itemId
- )
- ),
- ...addedPermissions,
- ]
- }
-
- 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..dd44bda69a 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("../../utilities/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/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..3be2e3fd41
--- /dev/null
+++ b/packages/server/src/api/controllers/routing.js
@@ -0,0 +1,17 @@
+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/controllers/user.js b/packages/server/src/api/controllers/user.js
index 5e4f963f5b..c51e1fd2b8 100644
--- a/packages/server/src/api/controllers/user.js
+++ b/packages/server/src/api/controllers/user.js
@@ -2,9 +2,11 @@ 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_LEVEL_ID_ARRAY,
+} = require("../../utilities/security/accessLevels")
+const {
+ BUILTIN_PERMISSION_NAMES,
+} = require("../../utilities/security/permissions")
exports.fetch = async function(ctx) {
const database = new CouchDB(ctx.user.appId)
@@ -18,7 +20,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.")
@@ -35,6 +43,7 @@ exports.create = async function(ctx) {
name: name || username,
type: "user",
accessLevelId,
+ permissions: permissions || [BUILTIN_PERMISSION_NAMES.POWER],
}
try {
@@ -89,10 +98,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_LEVEL_ID_ARRAY.indexOf(accessLevelId) !== -1) {
return {
_id: accessLevelId,
name: accessLevelId,
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/api/index.js b/packages/server/src/api/index.js
index 4a4e80d1ed..500ca50ff2 100644
--- a/packages/server/src/api/index.js
+++ b/packages/server/src/api/index.js
@@ -4,25 +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,
-} = require("./routes")
+const {mainRoutes, authRoutes, staticRoutes} = require("./routes")
const router = new Router()
const env = require("../environment")
@@ -72,52 +54,12 @@ 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())
diff --git a/packages/server/src/api/routes/accesslevel.js b/packages/server/src/api/routes/accesslevel.js
index af1ff80ec6..1f21a1ea29 100644
--- a/packages/server/src/api/routes/accesslevel.js
+++ b/packages/server/src/api/routes/accesslevel.js
@@ -1,14 +1,18 @@
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.save)
+ .get("/api/accesslevels", authorized(BUILDER), controller.fetch)
+ .get("/api/accesslevels/:levelId", authorized(BUILDER), controller.find)
+ .delete(
+ "/api/accesslevels/:levelId/:rev",
+ authorized(BUILDER),
+ controller.destroy
+ )
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/index.js b/packages/server/src/api/routes/index.js
index a19742097c..44f2d08509 100644
--- a/packages/server/src/api/routes/index.js
+++ b/packages/server/src/api/routes/index.js
@@ -15,10 +15,10 @@ const deployRoutes = require("./deploy")
const apiKeysRoutes = require("./apikeys")
const templatesRoutes = require("./templates")
const analyticsRoutes = require("./analytics")
+const routingRoutes = require("./routing")
-module.exports = {
+exports.mainRoutes = [
deployRoutes,
- authRoutes,
pageRoutes,
screenRoutes,
userRoutes,
@@ -26,7 +26,6 @@ module.exports = {
rowRoutes,
tableRoutes,
viewRoutes,
- staticRoutes,
componentRoutes,
automationRoutes,
accesslevelRoutes,
@@ -34,4 +33,8 @@ module.exports = {
templatesRoutes,
analyticsRoutes,
webhookRoutes,
-}
+ routingRoutes,
+]
+
+exports.authRoutes = authRoutes
+exports.staticRoutes = staticRoutes
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/routing.js b/packages/server/src/api/routes/routing.js
new file mode 100644
index 0000000000..60f84de781
--- /dev/null
+++ b/packages/server/src/api/routes/routing.js
@@ -0,0 +1,13 @@
+const Router = require("@koa/router")
+const authorized = require("../../middleware/authorized")
+const { BUILDER } = require("../../utilities/security/permissions")
+const controller = require("../controllers/routing")
+
+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)
+
+module.exports = 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..ce49f66043 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")
@@ -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.object({
+ route: Joi.string().required(),
+ accessLevelId: Joi.string().required().allow(""),
+ }).required().unknown(true),
props: Joi.object({
- _id: Joi.string().required(),
- _component: Joi.string().required(),
- _children: Joi.array().required(),
- _instanceName: Joi.string().required(),
- _styles: Joi.object().required(),
- type: Joi.string().optional(),
- table: Joi.string().optional(),
- }).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/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..eeb786f7d3 100644
--- a/packages/server/src/api/routes/tests/accesslevel.spec.js
+++ b/packages/server/src/api/routes/tests/accesslevel.spec.js
@@ -6,13 +6,10 @@ const {
defaultHeaders
} = require("./couchTestUtils")
const {
- generateAdminPermissions,
- generatePowerUserPermissions,
- POWERUSER_LEVEL_ID,
- ADMIN_LEVEL_ID,
- READ_TABLE,
- WRITE_TABLE,
-} = require("../../../utilities/accessLevels")
+ BUILTIN_LEVEL_IDS,
+} = require("../../../utilities/security/accessLevels")
+
+const accessLevelBody = { name: "user", inherits: BUILTIN_LEVEL_IDS.BASIC }
describe("/accesslevels", () => {
let server
@@ -41,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)
@@ -49,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([])
})
});
@@ -59,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: [ { itemId: table._id, name: READ_TABLE }] })
+ .send(accessLevelBody)
.set(defaultHeaders(appId))
.expect('Content-Type', /json/)
.expect(200)
@@ -74,16 +70,17 @@ 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_LEVEL_IDS.ADMIN)
+ expect(adminLevel.inherits).toEqual(BUILTIN_LEVEL_IDS.POWER)
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_LEVEL_IDS.POWER)
+ expect(powerUserLevel.inherits).toEqual(BUILTIN_LEVEL_IDS.BASIC)
expect(powerUserLevel).toBeDefined()
- expect(powerUserLevel.permissions).toEqual(await generatePowerUserPermissions(appId))
const customLevelFetched = res.body.find(r => r._id === customLevel._id)
- expect(customLevelFetched.permissions).toEqual(customLevel.permissions)
+ expect(customLevelFetched.inherits).toEqual(BUILTIN_LEVEL_IDS.BASIC)
+ expect(customLevelFetched).toBeDefined()
})
});
@@ -92,7 +89,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" })
.set(defaultHeaders(appId))
.expect('Content-Type', /json/)
.expect(200)
@@ -110,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: [ { itemId: table._id, name: READ_TABLE }] })
- .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: [ { itemId: table._id, name: WRITE_TABLE } ]
- })
- .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.some(p => p.name === WRITE_TABLE)).toBe(true)
- expect(finalRes.body.permissions.some(p => p.name === READ_TABLE)).toBe(true)
- })
-
- it("should remove given permissions", async () => {
- const createRes = await request
- .post(`/api/accesslevels`)
- .send({
- name: "user",
- permissions: [
- { itemId: table._id, name: READ_TABLE },
- { itemId: table._id, name: WRITE_TABLE },
- ]
- })
- .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: [ { itemId: table._id, name: WRITE_TABLE }]
- })
- .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.some(p => p.name === READ_TABLE)).toBe(true)
- })
- })
});
diff --git a/packages/server/src/api/routes/tests/couchTestUtils.js b/packages/server/src/api/routes/tests/couchTestUtils.js
index 02a75d77fd..0e137f1a44 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,
-} = require("../../../utilities/accessLevels")
+ BUILTIN_LEVEL_IDS,
+} = 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_LEVEL_IDS.BUILDER,
}
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_LEVEL_IDS.POWER,
})
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_LEVEL_IDS.POWER,
+ permissions,
})
const anonUser = {
userId: "ANON",
- accessLevelId: ANON_LEVEL_ID,
+ accessLevelId: BUILTIN_LEVEL_IDS.ANON,
appId: appId,
version: packageJson.version,
}
@@ -232,15 +216,10 @@ exports.testPermissionsForEndpoint = async ({
url,
body,
appId,
- permissionName,
- itemId,
+ permName1,
+ permName2,
}) => {
- const headers = await createUserWithOnePermission(
- request,
- appId,
- permissionName,
- itemId
- )
+ const headers = await createUserWithOnePermission(request, appId, permName1)
await createRequest(request, method, url, body)
.set(headers)
@@ -249,8 +228,7 @@ exports.testPermissionsForEndpoint = async ({
const noPermsHeaders = await createUserWithAllPermissionExceptOne(
request,
appId,
- permissionName,
- itemId
+ 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 a569902bae..f9277039ea 100644
--- a/packages/server/src/api/routes/tests/user.spec.js
+++ b/packages/server/src/api/routes/tests/user.spec.js
@@ -5,11 +5,12 @@ const {
createUser,
testPermissionsForEndpoint,
} = require("./couchTestUtils")
-const {
- POWERUSER_LEVEL_ID,
- LIST_USERS,
- USER_MANAGEMENT
-} = require("../../../utilities/accessLevels")
+const {
+ BUILTIN_PERMISSION_NAMES,
+} = require("../../../utilities/security/permissions")
+const {
+ BUILTIN_LEVEL_IDS,
+} = require("../../../utilities/security/accessLevels")
describe("/users", () => {
let request
@@ -53,7 +54,8 @@ describe("/users", () => {
method: "GET",
url: `/api/users`,
appId: appId,
- permissionName: LIST_USERS,
+ permName1: BUILTIN_PERMISSION_NAMES.POWER,
+ permName2: BUILTIN_PERMISSION_NAMES.WRITE,
})
})
@@ -65,7 +67,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_LEVEL_IDS.POWER })
.expect(200)
.expect('Content-Type', /json/)
@@ -77,10 +79,11 @@ 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_LEVEL_IDS.POWER },
url: `/api/users`,
appId: appId,
- permissionName: USER_MANAGEMENT,
+ permName1: BUILTIN_PERMISSION_NAMES.ADMIN,
+ permName2: BUILTIN_PERMISSION_NAMES.POWER,
})
})
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..4b6250ce36 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")
@@ -11,7 +11,7 @@ module.exports.definition = {
type: "ACTION",
stepId: "CREATE_USER",
inputs: {
- accessLevelId: accessLevels.POWERUSER_LEVEL_ID,
+ accessLevelId: accessLevels.BUILTIN_LEVEL_IDS.POWER,
},
schema: {
inputs: {
@@ -28,8 +28,8 @@ module.exports.definition = {
accessLevelId: {
type: "string",
title: "Access Level",
- enum: accessLevels.ACCESS_LEVELS,
- pretty: Object.values(accessLevels.PRETTY_ACCESS_LEVELS),
+ 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/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/middleware/authenticated.js b/packages/server/src/middleware/authenticated.js
index 529581f362..c0fbbdb86c 100644
--- a/packages/server/src/middleware/authenticated.js
+++ b/packages/server/src/middleware/authenticated.js
@@ -1,12 +1,7 @@
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")
+const { BUILTIN_LEVEL_ID_ARRAY } = require("../utilities/security/accessLevels")
const env = require("../environment")
const { AuthTypes } = require("../constants")
const { getAppId, getCookieName, setCookie } = require("../utilities")
@@ -74,12 +69,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_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 34758903f3..5f4b78b97e 100644
--- a/packages/server/src/middleware/authorized.js
+++ b/packages/server/src/middleware/authorized.js
@@ -1,17 +1,17 @@
+const { BUILTIN_LEVEL_IDS } = require("../utilities/security/accessLevels")
const {
- adminPermissions,
- ADMIN_LEVEL_ID,
- POWERUSER_LEVEL_ID,
- BUILDER_LEVEL_ID,
- BUILDER,
-} = require("../utilities/accessLevels")
+ PermissionTypes,
+ doesHavePermission,
+} = require("../utilities/security/permissions")
const env = require("../environment")
const { apiKeyTable } = require("../db/dynamoClient")
const { AuthTypes } = require("../constants")
+const ADMIN_ACCESS = [BUILTIN_LEVEL_IDS.ADMIN, BUILTIN_LEVEL_IDS.BUILDER]
+
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 +37,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 +47,19 @@ module.exports = (permName, getItemId) => async (ctx, next) => {
ctx.throw(403, "User not found")
}
- if (ctx.user.accessLevel._id === ADMIN_LEVEL_ID) {
+ const accessLevel = ctx.user.accessLevel
+ const permissions = ctx.user.permissions
+ if (ADMIN_ACCESS.indexOf(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}` : "")
-
- 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 (!doesHavePermission(permType, permLevel, permissions)) {
+ ctx.throw(403, "User does not have permission")
}
- if (
- ctx.user.accessLevel.permissions
- .map(permissionId)
- .includes(thisPermissionId)
- ) {
- return next()
- }
-
- ctx.throw(403, "Not Authorized")
+ return next()
}
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..f3adf079ad 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("../accessLevels")
+const { BUILTIN_LEVEL_IDS } = 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_LEVEL_IDS.BUILDER,
+ 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 e1513fd0fa..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("./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/routing/index.js b/packages/server/src/utilities/routing/index.js
new file mode 100644
index 0000000000..bb0fe5bb62
--- /dev/null
+++ b/packages/server/src/utilities/routing/index.js
@@ -0,0 +1,24 @@
+const CouchDB = require("../../db")
+const { createRoutingView } = require("./routingUtils")
+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), {
+ 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
+ if (err != null && err.name === "not_found") {
+ await createRoutingView(appId)
+ return exports.getRoutingInfo(appId)
+ } else {
+ throw err
+ }
+ }
+}
+
+exports.createRoutingView = createRoutingView
diff --git a/packages/server/src/utilities/routing/routingUtils.js b/packages/server/src/utilities/routing/routingUtils.js
new file mode 100644
index 0000000000..5f6a6b5312
--- /dev/null
+++ b/packages/server/src/utilities/routing/routingUtils.js
@@ -0,0 +1,24 @@
+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 = {
+ // 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,
+ })
+ }
+ }`,
+ }
+ designDoc.views = {
+ ...designDoc.views,
+ [ViewNames.ROUTING]: view,
+ }
+ await db.put(designDoc)
+}
diff --git a/packages/server/src/utilities/security/accessLevels.js b/packages/server/src/utilities/security/accessLevels.js
new file mode 100644
index 0000000000..e99b635f39
--- /dev/null
+++ b/packages/server/src/utilities/security/accessLevels.js
@@ -0,0 +1,112 @@
+const CouchDB = require("../../db")
+
+const BUILTIN_IDS = {
+ ADMIN: "ADMIN",
+ POWER: "POWER_USER",
+ BASIC: "BASIC",
+ ANON: "ANON",
+ BUILDER: "BUILDER",
+}
+
+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_NAME_ARRAY = Object.values(exports.BUILTIN_LEVELS).map(
+ level => level.name
+)
+
+function isBuiltin(accessLevel) {
+ return exports.BUILTIN_LEVEL_ID_ARRAY.indexOf(accessLevel) !== -1
+}
+
+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
+ }
+
+ 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
+ }
+ 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
+ }
+}
+
+exports.AccessController = AccessController
+exports.BUILTIN_LEVEL_IDS = BUILTIN_IDS
+exports.isBuiltin = isBuiltin
+exports.AccessLevel = AccessLevel
diff --git a/packages/server/src/utilities/security/permissions.js b/packages/server/src/utilities/security/permissions.js
new file mode 100644
index 0000000000..d19f31e393
--- /dev/null
+++ b/packages/server/src/utilities/security/permissions.js
@@ -0,0 +1,113 @@
+const { flatten } = require("lodash")
+
+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 []
+ }
+}
+
+exports.BUILTIN_PERMISSION_NAMES = {
+ READ_ONLY: "read_only",
+ WRITE: "write",
+ ADMIN: "admin",
+ POWER: "power",
+}
+
+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),
+ ],
+ },
+ 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) => {
+ 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
diff --git a/packages/server/yarn.lock b/packages/server/yarn.lock
index 4348049d51..ee09ecd3b5 100644
--- a/packages/server/yarn.lock
+++ b/packages/server/yarn.lock
@@ -200,10 +200,10 @@
lodash "^4.17.19"
to-fast-properties "^2.0.0"
-"@budibase/client@^0.3.7":
- version "0.3.7"
- resolved "https://registry.yarnpkg.com/@budibase/client/-/client-0.3.7.tgz#8ed2d40d91ba3788a69ee5db5078f757adb4187f"
- integrity sha512-EgpHfw/WOUYeCG4cILDbaN2WFBDSPS698Z+So7FP5l+4E1fvmqtpXVKJYsviwYEx8AKKYyU3nuDi0l6xzb5Flg==
+"@budibase/client@^0.3.8":
+ 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"