diff --git a/packages/builder/src/builderStore/api.js b/packages/builder/src/builderStore/api.js index 3fcd35ce28..116222409f 100644 --- a/packages/builder/src/builderStore/api.js +++ b/packages/builder/src/builderStore/api.js @@ -1,17 +1,13 @@ const apiCall = method => async (url, body) => { + const headers = { + "Content-Type": "application/json", + } const response = await fetch(url, { method: method, - headers: { - "Content-Type": "application/json", - "x-user-agent": "Budibase Builder", - }, body: body && JSON.stringify(body), + headers, }) - // if (response.status === 500) { - // throw new Error("Server Error"); - // } - return response } @@ -22,9 +18,9 @@ export const del = apiCall("DELETE") export const put = apiCall("PUT") export default { - post, - get, - patch, - delete: del, - put, + post: apiCall("POST"), + get: apiCall("GET"), + patch: apiCall("PATCH"), + delete: apiCall("DELETE"), + put: apiCall("PUT"), } diff --git a/packages/builder/src/builderStore/store/backend.js b/packages/builder/src/builderStore/store/backend.js index 9a5a7b1fbc..53bdaa3360 100644 --- a/packages/builder/src/builderStore/store/backend.js +++ b/packages/builder/src/builderStore/store/backend.js @@ -24,8 +24,8 @@ export const getBackendUiStore = () => { store.actions = { database: { select: async db => { - const modelsResponse = await api.get(`/api/${db._id}/models`) - const viewsResponse = await api.get(`/api/${db._id}/views`) + const modelsResponse = await api.get(`/api/models`) + const viewsResponse = await api.get(`/api/views`) const models = await modelsResponse.json() const views = await viewsResponse.json() store.update(state => { diff --git a/packages/builder/src/builderStore/store/index.js b/packages/builder/src/builderStore/store/index.js index 1f3ea9c0ef..0b17e8c61c 100644 --- a/packages/builder/src/builderStore/store/index.js +++ b/packages/builder/src/builderStore/store/index.js @@ -1,5 +1,4 @@ import { values } from "lodash/fp" -import { backendUiStore } from "builderStore" import * as backendStoreActions from "./backend" import { writable, get } from "svelte/store" import api from "../api" @@ -295,13 +294,10 @@ const addChildComponent = store => (componentToAdd, presetName) => { const presetProps = presetName ? component.presets[presetName] : {} - const instanceId = get(backendUiStore).selectedDatabase._id - const newComponent = createProps( component, { ...presetProps, - _instanceId: instanceId, }, state ) diff --git a/packages/builder/src/builderStore/store/workflow/index.js b/packages/builder/src/builderStore/store/workflow/index.js index 8d5e63b197..9d50f85661 100644 --- a/packages/builder/src/builderStore/store/workflow/index.js +++ b/packages/builder/src/builderStore/store/workflow/index.js @@ -3,8 +3,8 @@ import api from "../../api" import Workflow from "./Workflow" const workflowActions = store => ({ - fetch: async instanceId => { - const WORKFLOWS_URL = `/api/${instanceId}/workflows` + fetch: async () => { + const WORKFLOWS_URL = `/api/workflows` const workflowResponse = await api.get(WORKFLOWS_URL) const json = await workflowResponse.json() store.update(state => { @@ -12,14 +12,14 @@ const workflowActions = store => ({ return state }) }, - create: async ({ instanceId, name }) => { + create: async ({ name }) => { const workflow = { name, definition: { steps: [], }, } - const CREATE_WORKFLOW_URL = `/api/${instanceId}/workflows` + const CREATE_WORKFLOW_URL = `/api/workflows` const response = await api.post(CREATE_WORKFLOW_URL, workflow) const json = await response.json() store.update(state => { @@ -28,8 +28,8 @@ const workflowActions = store => ({ return state }) }, - save: async ({ instanceId, workflow }) => { - const UPDATE_WORKFLOW_URL = `/api/${instanceId}/workflows` + save: async ({ workflow }) => { + const UPDATE_WORKFLOW_URL = `/api/workflows` const response = await api.put(UPDATE_WORKFLOW_URL, workflow) const json = await response.json() store.update(state => { @@ -42,8 +42,8 @@ const workflowActions = store => ({ return state }) }, - update: async ({ instanceId, workflow }) => { - const UPDATE_WORKFLOW_URL = `/api/${instanceId}/workflows` + update: async ({ workflow }) => { + const UPDATE_WORKFLOW_URL = `/api/workflows` const response = await api.put(UPDATE_WORKFLOW_URL, workflow) const json = await response.json() store.update(state => { @@ -55,9 +55,9 @@ const workflowActions = store => ({ return state }) }, - delete: async ({ instanceId, workflow }) => { + delete: async ({ workflow }) => { const { _id, _rev } = workflow - const DELETE_WORKFLOW_URL = `/api/${instanceId}/workflows/${_id}/${_rev}` + const DELETE_WORKFLOW_URL = `/api/workflows/${_id}/${_rev}` await api.delete(DELETE_WORKFLOW_URL) store.update(state => { diff --git a/packages/builder/src/components/database/ModelDataTable/ModelDataTable.svelte b/packages/builder/src/components/database/ModelDataTable/ModelDataTable.svelte index f1d49894a0..8eb6defa8f 100644 --- a/packages/builder/src/components/database/ModelDataTable/ModelDataTable.svelte +++ b/packages/builder/src/components/database/ModelDataTable/ModelDataTable.svelte @@ -53,12 +53,10 @@ let views = [] let currentPage = 0 - $: instanceId = $backendUiStore.selectedDatabase._id - $: { if ($backendUiStore.selectedView) { api - .fetchDataForView($backendUiStore.selectedView, instanceId) + .fetchDataForView($backendUiStore.selectedView) .then(records => { data = records || [] headers = Object.keys($backendUiStore.selectedModel.schema).filter( diff --git a/packages/builder/src/components/database/ModelDataTable/api.js b/packages/builder/src/components/database/ModelDataTable/api.js index cb98879567..f50290b091 100644 --- a/packages/builder/src/components/database/ModelDataTable/api.js +++ b/packages/builder/src/components/database/ModelDataTable/api.js @@ -1,7 +1,7 @@ import api from "builderStore/api" -export async function createUser(user, instanceId) { - const CREATE_USER_URL = `/api/${instanceId}/users` +export async function createUser(user) { + const CREATE_USER_URL = `/api/users` const response = await api.post(CREATE_USER_URL, user) return await response.json() } @@ -14,21 +14,21 @@ export async function createDatabase(appname, instanceName) { return await response.json() } -export async function deleteRecord(record, instanceId) { - const DELETE_RECORDS_URL = `/api/${instanceId}/${record._modelId}/records/${record._id}/${record._rev}` +export async function deleteRecord(record) { + const DELETE_RECORDS_URL = `/api/${record._modelId}/records/${record._id}/${record._rev}` const response = await api.delete(DELETE_RECORDS_URL) return response } -export async function saveRecord(record, instanceId, modelId) { - const SAVE_RECORDS_URL = `/api/${instanceId}/${modelId}/records` +export async function saveRecord(record, modelId) { + const SAVE_RECORDS_URL = `/api/${modelId}/records` const response = await api.post(SAVE_RECORDS_URL, record) return await response.json() } -export async function fetchDataForView(viewName, instanceId) { - const FETCH_RECORDS_URL = `/api/${instanceId}/views/${viewName}` +export async function fetchDataForView(viewName) { + const FETCH_RECORDS_URL = `/api/views/${viewName}` const response = await api.get(FETCH_RECORDS_URL) return await response.json() diff --git a/packages/builder/src/components/database/ModelDataTable/modals/CreateEditModel/CreateEditModel.svelte b/packages/builder/src/components/database/ModelDataTable/modals/CreateEditModel/CreateEditModel.svelte index 6264b74e09..9e80ec4ca1 100644 --- a/packages/builder/src/components/database/ModelDataTable/modals/CreateEditModel/CreateEditModel.svelte +++ b/packages/builder/src/components/database/ModelDataTable/modals/CreateEditModel/CreateEditModel.svelte @@ -18,7 +18,6 @@ let fieldToEdit $: modelFields = model.schema ? Object.entries(model.schema) : [] - $: instanceId = $backendUiStore.selectedDatabase._id function editField() {} @@ -27,7 +26,7 @@ function onFinishedFieldEdit() {} async function saveModel() { - const SAVE_MODEL_URL = `/api/${instanceId}/models` + const SAVE_MODEL_URL = `/api/models` const response = await api.post(SAVE_MODEL_URL, model) const newModel = await response.json() backendUiStore.actions.models.create(newModel) diff --git a/packages/builder/src/components/database/ModelDataTable/modals/CreateEditRecord.svelte b/packages/builder/src/components/database/ModelDataTable/modals/CreateEditRecord.svelte index 62688d58e3..a97cdc0ca1 100644 --- a/packages/builder/src/components/database/ModelDataTable/modals/CreateEditRecord.svelte +++ b/packages/builder/src/components/database/ModelDataTable/modals/CreateEditRecord.svelte @@ -14,8 +14,6 @@ let errors = [] let selectedModel - $: instanceId = $backendUiStore.selectedDatabase._id - $: modelSchema = $backendUiStore.selectedModel ? Object.entries($backendUiStore.selectedModel.schema) : [] @@ -49,7 +47,6 @@ ...record, modelId: $backendUiStore.selectedModel._id, }, - instanceId, $backendUiStore.selectedModel._id ) if (recordResponse.errors) { diff --git a/packages/builder/src/components/database/ModelDataTable/modals/CreateEditView.svelte b/packages/builder/src/components/database/ModelDataTable/modals/CreateEditView.svelte index 4db445e54f..53cb3e2cc6 100644 --- a/packages/builder/src/components/database/ModelDataTable/modals/CreateEditView.svelte +++ b/packages/builder/src/components/database/ModelDataTable/modals/CreateEditView.svelte @@ -29,7 +29,7 @@ function deleteView() {} async function saveView() { - const SAVE_VIEW_URL = `/api/${instanceId}/views` + const SAVE_VIEW_URL = `/api/views` const response = await api.post(SAVE_VIEW_URL, view) backendUiStore.update(state => { state.views = [...state.views, response.view] diff --git a/packages/builder/src/components/database/ModelDataTable/modals/CreateUser.svelte b/packages/builder/src/components/database/ModelDataTable/modals/CreateUser.svelte index d5c924405f..60b77111d3 100644 --- a/packages/builder/src/components/database/ModelDataTable/modals/CreateUser.svelte +++ b/packages/builder/src/components/database/ModelDataTable/modals/CreateUser.svelte @@ -10,12 +10,11 @@ let accessLevelId $: valid = username && password && accessLevelId - $: instanceId = $backendUiStore.selectedDatabase._id $: appId = $store.appId async function createUser() { const user = { name: username, username, password, accessLevelId } - const response = await api.createUser(user, instanceId) + const response = await api.createUser(user) backendUiStore.actions.users.create(response) onClosed() } diff --git a/packages/builder/src/components/database/ModelDataTable/modals/DeleteRecord.svelte b/packages/builder/src/components/database/ModelDataTable/modals/DeleteRecord.svelte index 05ecfcd85f..aa192b32eb 100644 --- a/packages/builder/src/components/database/ModelDataTable/modals/DeleteRecord.svelte +++ b/packages/builder/src/components/database/ModelDataTable/modals/DeleteRecord.svelte @@ -6,7 +6,6 @@ export let record export let onClosed - $: instanceId = $backendUiStore.selectedDatabase._id
@@ -25,7 +24,7 @@ { - await api.deleteRecord(record, instanceId) + await api.deleteRecord(record) backendUiStore.actions.records.delete(record) onClosed() }}> diff --git a/packages/builder/src/components/nav/HierarchyRow.svelte b/packages/builder/src/components/nav/HierarchyRow.svelte index 8921ab74de..7653e46fd0 100644 --- a/packages/builder/src/components/nav/HierarchyRow.svelte +++ b/packages/builder/src/components/nav/HierarchyRow.svelte @@ -7,7 +7,6 @@ CreateEditModelModal, CreateEditViewModal, } from "components/database/ModelDataTable/modals" - import api from "builderStore/api" const { open, close } = getContext("simple-modal") diff --git a/packages/builder/src/components/nav/SchemaManagementDrawer.svelte b/packages/builder/src/components/nav/SchemaManagementDrawer.svelte index cc9e5d86a7..9465c85140 100644 --- a/packages/builder/src/components/nav/SchemaManagementDrawer.svelte +++ b/packages/builder/src/components/nav/SchemaManagementDrawer.svelte @@ -52,7 +52,7 @@ } async function deleteModel(modelToDelete) { - const DELETE_MODEL_URL = `/api/${instanceId}/models/${node._id}/${node._rev}` + const DELETE_MODEL_URL = `/api/models/${node._id}/${node._rev}` const response = await api.delete(DELETE_MODEL_URL) backendUiStore.update(state => { state.models = state.models.filter( diff --git a/packages/builder/src/components/nav/UsersList.svelte b/packages/builder/src/components/nav/UsersList.svelte index faef8a4234..e213aa066c 100644 --- a/packages/builder/src/components/nav/UsersList.svelte +++ b/packages/builder/src/components/nav/UsersList.svelte @@ -12,11 +12,10 @@ $: currentAppInfo = { appname: $store.appname, - instanceId: $backendUiStore.selectedDatabase._id, } async function fetchUsers() { - const FETCH_USERS_URL = `/api/${currentAppInfo.instanceId}/users` + const FETCH_USERS_URL = `/api/users` const response = await api.get(FETCH_USERS_URL) const users = await response.json() backendUiStore.update(state => { diff --git a/packages/builder/src/components/userInterface/AppPreview/CurrentItemPreview.svelte b/packages/builder/src/components/userInterface/AppPreview/CurrentItemPreview.svelte index 00d7e6c171..523ed25031 100644 --- a/packages/builder/src/components/userInterface/AppPreview/CurrentItemPreview.svelte +++ b/packages/builder/src/components/userInterface/AppPreview/CurrentItemPreview.svelte @@ -91,7 +91,6 @@ ? screenPlaceholder : $store.currentPreviewItem, ], - appRootPath: "", } $: selectedComponentType = getComponentTypeName($store.currentComponentInfo) @@ -108,6 +107,8 @@ selectedComponentType, selectedComponentId, frontendDefinition, + appId: $store.appId, + instanceId: $backendUiStore.selectedDatabase._id, }) ) } diff --git a/packages/builder/src/components/userInterface/AppPreview/iframeTemplate.js b/packages/builder/src/components/userInterface/AppPreview/iframeTemplate.js index 3d305f1c1d..c9272dddb2 100644 --- a/packages/builder/src/components/userInterface/AppPreview/iframeTemplate.js +++ b/packages/builder/src/components/userInterface/AppPreview/iframeTemplate.js @@ -20,6 +20,7 @@ export default ` font-weight: bold; } + diff --git a/packages/builder/src/components/workflow/SetupPanel/DeleteWorkflowModal.svelte b/packages/builder/src/components/workflow/SetupPanel/DeleteWorkflowModal.svelte index 9f51362419..4eed82bf25 100644 --- a/packages/builder/src/components/workflow/SetupPanel/DeleteWorkflowModal.svelte +++ b/packages/builder/src/components/workflow/SetupPanel/DeleteWorkflowModal.svelte @@ -1,7 +1,6 @@ diff --git a/packages/builder/src/components/workflow/WorkflowPanel/WorkflowPanel.svelte b/packages/builder/src/components/workflow/WorkflowPanel/WorkflowPanel.svelte index f1a1357978..ffa9e8f408 100644 --- a/packages/builder/src/components/workflow/WorkflowPanel/WorkflowPanel.svelte +++ b/packages/builder/src/components/workflow/WorkflowPanel/WorkflowPanel.svelte @@ -2,7 +2,6 @@ import { onMount } from "svelte" import { backendUiStore, workflowStore } from "builderStore" import { WorkflowList, BlockList } from "./" - import api from "builderStore/api" import blockDefinitions from "./blockDefinitions" let selectedTab = "WORKFLOWS" diff --git a/packages/builder/src/pages/[application]/workflow/[workflow]/_layout.svelte b/packages/builder/src/pages/[application]/workflow/[workflow]/_layout.svelte index f1b3bae003..a00f7b4ab0 100644 --- a/packages/builder/src/pages/[application]/workflow/[workflow]/_layout.svelte +++ b/packages/builder/src/pages/[application]/workflow/[workflow]/_layout.svelte @@ -1,3 +1,4 @@ diff --git a/packages/client/package.json b/packages/client/package.json index 01b90a7bc5..acefff3770 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -15,7 +15,7 @@ "client": "web" } }, - "testURL": "http://jest-breaks-if-this-does-not-exist", + "testURL": "http://test.com", "moduleNameMapper": { "\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "/internals/mocks/fileMock.js", "\\.(css|less|sass|scss)$": "identity-obj-proxy" diff --git a/packages/client/src/api/index.js b/packages/client/src/api/index.js index f019897494..373ac1809e 100644 --- a/packages/client/src/api/index.js +++ b/packages/client/src/api/index.js @@ -1,9 +1,9 @@ import { authenticate } from "./authenticate" import { triggerWorkflow } from "./workflow" -export const createApi = ({ rootPath = "", setState, getState }) => { +export const createApi = ({ setState, getState }) => { const apiCall = method => async ({ url, body }) => { - const response = await fetch(`${rootPath}${url}`, { + const response = await fetch(url, { method: method, headers: { "Content-Type": "application/json", @@ -45,7 +45,6 @@ export const createApi = ({ rootPath = "", setState, getState }) => { const isSuccess = obj => !obj || !obj[ERROR_MEMBER] const apiOpts = { - rootPath, setState, getState, isSuccess, diff --git a/packages/client/src/createApp.js b/packages/client/src/createApp.js index a80c1009c7..9e1a9ea1b1 100644 --- a/packages/client/src/createApp.js +++ b/packages/client/src/createApp.js @@ -2,6 +2,7 @@ import { attachChildren } from "./render/attachChildren" import { createTreeNode } from "./render/prepareRenderComponent" import { screenRouter } from "./render/screenRouter" import { createStateManager } from "./state/stateManager" +import { getAppId } from "./render/getAppId" export const createApp = ({ componentLibraries, @@ -15,11 +16,9 @@ export const createApp = ({ const onScreenSlotRendered = screenSlotNode => { const onScreenSelected = (screen, url) => { const stateManager = createStateManager({ - frontendDefinition, componentLibraries, onScreenSlotRendered: () => {}, routeTo, - appRootPath: frontendDefinition.appRootPath, }) const getAttachChildrenParams = attachChildrenParams(stateManager) screenSlotNode.props._children = [screen.props] @@ -36,10 +35,10 @@ export const createApp = ({ routeTo = screenRouter({ screens: frontendDefinition.screens, onScreenSelected, - appRootPath: frontendDefinition.appRootPath, + window, }) const fallbackPath = window.location.pathname.replace( - frontendDefinition.appRootPath, + getAppId(window.document.cookie), "" ) routeTo(currentUrl || fallbackPath) @@ -59,10 +58,8 @@ export const createApp = ({ let rootTreeNode const pageStateManager = createStateManager({ - frontendDefinition, componentLibraries, onScreenSlotRendered, - appRootPath: frontendDefinition.appRootPath, // seems weird, but the routeTo variable may not be available at this point routeTo: url => routeTo(url), }) diff --git a/packages/client/src/index.js b/packages/client/src/index.js index 95f722d445..87813a812d 100644 --- a/packages/client/src/index.js +++ b/packages/client/src/index.js @@ -1,5 +1,6 @@ import { createApp } from "./createApp" import { builtins, builtinLibName } from "./render/builtinComponents" +import { getAppId } from "./render/getAppId" /** * create a web application from static budibase definition files. @@ -8,7 +9,7 @@ import { builtins, builtinLibName } from "./render/builtinComponents" export const loadBudibase = async opts => { const _window = (opts && opts.window) || window // const _localStorage = (opts && opts.localStorage) || localStorage - + const appId = getAppId(_window.document.cookie) const frontendDefinition = _window["##BUDIBASE_FRONTEND_DEFINITION##"] const user = {} @@ -20,9 +21,7 @@ export const loadBudibase = async opts => { for (let library of libraries) { // fetch the JavaScript for the component libraries from the server componentLibraryModules[library] = await import( - `/${frontendDefinition.appId}/componentlibrary?library=${encodeURI( - library - )}` + `/componentlibrary?library=${encodeURI(library)}` ) } @@ -38,11 +37,11 @@ export const loadBudibase = async opts => { componentLibraries: componentLibraryModules, frontendDefinition, user, - window, + window: _window, }) const route = _window.location - ? _window.location.pathname.replace(frontendDefinition.appRootPath, "") + ? _window.location.pathname.replace(`${appId}/`, "").replace(appId, "") : "" initialisePage(frontendDefinition.page, _window.document.body, route) diff --git a/packages/client/src/render/getAppId.js b/packages/client/src/render/getAppId.js new file mode 100644 index 0000000000..6d3df8eac2 --- /dev/null +++ b/packages/client/src/render/getAppId.js @@ -0,0 +1,12 @@ +export const getAppId = docCookie => { + const cookie = + docCookie.split(";").find(c => c.trim().startsWith("budibase:token")) || + docCookie.split(";").find(c => c.trim().startsWith("builder:token")) + + const base64Token = cookie.substring(lengthOfKey) + + const user = JSON.parse(atob(base64Token.split(".")[1])) + return user.appId +} + +const lengthOfKey = "budibase:token=".length diff --git a/packages/client/src/render/screenRouter.js b/packages/client/src/render/screenRouter.js index 64319102f8..fb0b005096 100644 --- a/packages/client/src/render/screenRouter.js +++ b/packages/client/src/render/screenRouter.js @@ -1,11 +1,20 @@ import regexparam from "regexparam" import { routerStore } from "../state/store" +import { getAppId } from "./getAppId" -export const screenRouter = ({ screens, onScreenSelected, appRootPath }) => { +export const screenRouter = ({ screens, onScreenSelected, window }) => { const makeRootedPath = url => { - if (appRootPath) { - if (url) return `${appRootPath}${url.startsWith("/") ? "" : "/"}${url}` - return appRootPath + if ( + window.location && + (window.location.hostname === "localhost" || + window.location.hostname === "127.0.0.1") + ) { + const appId = getAppId(window.document.cookie) + if (url) { + if (url.startsWith(appId)) return url + return `/${appId}${url.startsWith("/") ? "" : "/"}${url}` + } + return appId } return url } @@ -70,7 +79,7 @@ export const screenRouter = ({ screens, onScreenSelected, appRootPath }) => { ) return - const target = x.target || "_self" + const target = (x && x.target) || "_self" if (!y || target !== "_self" || x.host !== location.host) return e.preventDefault() diff --git a/packages/client/src/state/bbComponentApi.js b/packages/client/src/state/bbComponentApi.js index 7366a90f5c..112e3e8534 100644 --- a/packages/client/src/state/bbComponentApi.js +++ b/packages/client/src/state/bbComponentApi.js @@ -6,31 +6,19 @@ export const trimSlash = str => str.replace(/^\/+|\/+$/g, "") export const bbFactory = ({ store, - frontendDefinition, componentLibraries, onScreenSlotRendered, }) => { - const relativeUrl = url => { - if (!frontendDefinition.appRootPath) return url - if ( - url.startsWith("http:") || - url.startsWith("https:") || - url.startsWith("./") - ) - return url - - return frontendDefinition.appRootPath + "/" + trimSlash(url) - } - - const apiCall = method => (url, body) => - fetch(url, { + const apiCall = method => (url, body) => { + return fetch(url, { method: method, headers: { "Content-Type": "application/json", - "x-user-agent": "Budibase Builder", }, body: body && JSON.stringify(body), + credentials: "same-origin", }) + } const api = { post: apiCall("POST"), @@ -63,7 +51,6 @@ export const bbFactory = ({ getContext: getContext(treeNode), setContext: setContext(treeNode), store: store, - relativeUrl, api, parent, } diff --git a/packages/client/src/state/eventHandlers.js b/packages/client/src/state/eventHandlers.js index b013956dd4..cab825c24b 100644 --- a/packages/client/src/state/eventHandlers.js +++ b/packages/client/src/state/eventHandlers.js @@ -6,14 +6,13 @@ import { createApi } from "../api" export const EVENT_TYPE_MEMBER_NAME = "##eventHandlerType" -export const eventHandlers = (rootPath, routeTo) => { +export const eventHandlers = routeTo => { const handler = (parameters, execute) => ({ execute, parameters, }) const api = createApi({ - rootPath, setState, getState: (path, fallback) => getState(path, fallback), }) diff --git a/packages/client/src/state/stateManager.js b/packages/client/src/state/stateManager.js index 8e777ca4b9..5c5f198956 100644 --- a/packages/client/src/state/stateManager.js +++ b/packages/client/src/state/stateManager.js @@ -21,13 +21,11 @@ const isMetaProp = propName => propName === "_styles" export const createStateManager = ({ - appRootPath, - frontendDefinition, componentLibraries, onScreenSlotRendered, routeTo, }) => { - let handlerTypes = eventHandlers(appRootPath, routeTo) + let handlerTypes = eventHandlers(routeTo) let currentState const getCurrentState = () => currentState @@ -35,7 +33,6 @@ export const createStateManager = ({ const bb = bbFactory({ store: appStore, getCurrentState, - frontendDefinition, componentLibraries, onScreenSlotRendered, }) diff --git a/packages/client/tests/screenRouting.spec.js b/packages/client/tests/screenRouting.spec.js index bb4380196f..3e4818961d 100644 --- a/packages/client/tests/screenRouting.spec.js +++ b/packages/client/tests/screenRouting.spec.js @@ -18,7 +18,7 @@ describe("screenRouting", () => { it("should load correct screen, for initial URL, when appRootPath is something", async () => { const { page, screens } = pageWith3Screens() - const { dom } = await load(page, screens, "/testApp/screen2", "/testApp") + const { dom } = await load(page, screens, "/TEST_APP_ID/screen2", "127.0.0.1") const rootDiv = dom.window.document.body.children[0] expect(rootDiv.children.length).toBe(1) @@ -50,8 +50,8 @@ describe("screenRouting", () => { const { dom, app } = await load( page, screens, - "/testApp/screen2", - "/testApp" + "/TEST_APP_ID/screen2", + "127.0.0.1" ) app.routeTo()("/screen3") diff --git a/packages/client/tests/testAppDef.js b/packages/client/tests/testAppDef.js index c02c18a3b2..76bb3784b0 100644 --- a/packages/client/tests/testAppDef.js +++ b/packages/client/tests/testAppDef.js @@ -1,19 +1,33 @@ -import { JSDOM } from "jsdom" +import jsdom, { JSDOM } from "jsdom" import { loadBudibase } from "../src/index" -export const load = async (page, screens, url, appRootPath) => { +export const load = async (page, screens, url, host = "test.com") => { screens = screens || [] url = url || "/" - appRootPath = appRootPath || "" + + const fullUrl = `http://${host}${url}` + const cookieJar = new jsdom.CookieJar() + const cookie = `${btoa("{}")}.${btoa('{"appId":"TEST_APP_ID"}')}.signature` + cookieJar.setCookie( + `budibase:token=${cookie};domain=${host};path=/`, + fullUrl, + { + looseMode: false, + }, + () => {} + ) + const dom = new JSDOM("", { - url: `http://test${url}`, + url: fullUrl, + cookieJar, }) + autoAssignIds(page.props) for (let s of screens) { autoAssignIds(s.props) } setAppDef(dom.window, page, screens) - addWindowGlobals(dom.window, page, screens, appRootPath, { + addWindowGlobals(dom.window, page, screens, { hierarchy: {}, actions: [], triggers: [], @@ -27,11 +41,10 @@ export const load = async (page, screens, url, appRootPath) => { return { dom, app } } -const addWindowGlobals = (window, page, screens, appRootPath) => { +const addWindowGlobals = (window, page, screens) => { window["##BUDIBASE_FRONTEND_DEFINITION##"] = { page, screens, - appRootPath, } } @@ -88,7 +101,6 @@ const setAppDef = (window, page, screens) => { componentLibraries: [], page, screens, - appRootPath: "", } } diff --git a/packages/materialdesign-components/src/Test/createApp.js b/packages/materialdesign-components/src/Test/createApp.js index 96106635b7..0bd3098688 100644 --- a/packages/materialdesign-components/src/Test/createApp.js +++ b/packages/materialdesign-components/src/Test/createApp.js @@ -14,12 +14,6 @@ export default async () => { componentLibraries["@budibase/standard-components"] = standardcomponents const appDef = { hierarchy: {}, actions: {} } const user = { name: "yeo", permissions: [] } - const { initialisePage } = createApp( - componentLibraries, - { appRootPath: "" }, - appDef, - user, - {} - ) + const { initialisePage } = createApp(componentLibraries, {}, appDef, user, {}) return initialisePage } diff --git a/packages/server/src/api/controllers/accesslevel.js b/packages/server/src/api/controllers/accesslevel.js index 9c9c7ff727..44b638ed41 100644 --- a/packages/server/src/api/controllers/accesslevel.js +++ b/packages/server/src/api/controllers/accesslevel.js @@ -8,7 +8,7 @@ const { } = require("../../utilities/accessLevels") exports.fetch = async function(ctx) { - const db = new CouchDB(ctx.params.instanceId) + const db = new CouchDB(ctx.user.instanceId) const body = await db.query("database/by_type", { include_docs: true, key: ["accesslevel"], @@ -19,12 +19,12 @@ exports.fetch = async function(ctx) { { _id: ADMIN_LEVEL_ID, name: "Admin", - permissions: await generateAdminPermissions(ctx.params.instanceId), + permissions: await generateAdminPermissions(ctx.user.instanceId), }, { _id: POWERUSER_LEVEL_ID, name: "Power User", - permissions: await generatePowerUserPermissions(ctx.params.instanceId), + permissions: await generatePowerUserPermissions(ctx.user.instanceId), }, ] @@ -32,12 +32,12 @@ exports.fetch = async function(ctx) { } exports.find = async function(ctx) { - const db = new CouchDB(ctx.params.instanceId) + const db = new CouchDB(ctx.user.instanceId) ctx.body = await db.get(ctx.params.levelId) } exports.update = async function(ctx) { - const db = new CouchDB(ctx.params.instanceId) + const db = new CouchDB(ctx.user.instanceId) const level = await db.get(ctx.params.levelId) level.name = ctx.body.name level.permissions = ctx.request.body.permissions @@ -48,7 +48,7 @@ exports.update = async function(ctx) { } exports.patch = async function(ctx) { - const db = new CouchDB(ctx.params.instanceId) + const db = new CouchDB(ctx.user.instanceId) const level = await db.get(ctx.params.levelId) const { removedPermissions, addedPermissions, _rev } = ctx.request.body @@ -84,7 +84,7 @@ exports.patch = async function(ctx) { } exports.create = async function(ctx) { - const db = new CouchDB(ctx.params.instanceId) + const db = new CouchDB(ctx.user.instanceId) const level = { name: ctx.request.body.name, @@ -101,7 +101,7 @@ exports.create = async function(ctx) { } exports.destroy = async function(ctx) { - const db = new CouchDB(ctx.params.instanceId) + const db = new CouchDB(ctx.user.instanceId) await db.remove(ctx.params.levelId, ctx.params.rev) ctx.message = `Access Level ${ctx.params.id} deleted successfully` ctx.status = 200 diff --git a/packages/server/src/api/controllers/application.js b/packages/server/src/api/controllers/application.js index 0f604be0e7..ab4820794f 100644 --- a/packages/server/src/api/controllers/application.js +++ b/packages/server/src/api/controllers/application.js @@ -9,6 +9,7 @@ const { copy, exists, readFile, writeFile } = require("fs-extra") const { budibaseAppsDir } = require("../../utilities/budibaseDir") const { exec } = require("child_process") const sqrl = require("squirrelly") +const setBuilderToken = require("../../utilities/builder/setBuilderToken") exports.fetch = async function(ctx) { const db = new CouchDB(ClientDb.name(getClientId(ctx))) @@ -25,6 +26,14 @@ exports.fetchAppPackage = async function(ctx) { const db = new CouchDB(ClientDb.name(clientId)) const application = await db.get(ctx.params.applicationId) ctx.body = await getPackageForBuilder(ctx.config, application) + /* + instance is hardcoded now - this can only change when we move + pages and screens into the database + */ + const devInstance = application.instances.find( + i => i.name === `dev-${clientId}` + ) + setBuilderToken(ctx, ctx.params.applicationId, devInstance._id) } exports.create = async function(ctx) { @@ -37,10 +46,12 @@ exports.create = async function(ctx) { const appId = newid() // insert an appId -> clientId lookup const masterDb = new CouchDB("clientAppLookup") + await masterDb.put({ _id: appId, clientId, }) + const db = new CouchDB(ClientDb.name(clientId)) const newApplication = { @@ -56,18 +67,18 @@ exports.create = async function(ctx) { description: ctx.request.body.description, } - const { rev } = await db.post(newApplication) + const { rev } = await db.put(newApplication) newApplication._rev = rev - const createInstCtx = { - params: { - applicationId: newApplication._id, + user: { + appId: newApplication._id, }, request: { body: { name: `dev-${clientId}` }, }, } await instanceController.create(createInstCtx) + newApplication.instances.push(createInstCtx.body) if (ctx.isDev) { const newAppFolder = await createEmptyAppPackage(ctx, newApplication) diff --git a/packages/server/src/api/controllers/auth.js b/packages/server/src/api/controllers/auth.js index 5ec3aea617..e9570e270f 100644 --- a/packages/server/src/api/controllers/auth.js +++ b/packages/server/src/api/controllers/auth.js @@ -4,25 +4,31 @@ const ClientDb = require("../../db/clientDb") const bcrypt = require("../../utilities/bcrypt") exports.authenticate = async ctx => { + if (!ctx.user.appId) ctx.throw(400, "No appId") + const { username, password } = ctx.request.body if (!username) ctx.throw(400, "Username Required.") if (!password) ctx.throw(400, "Password Required") const masterDb = new CouchDB("clientAppLookup") - const { clientId } = await masterDb.get(ctx.params.appId) + + const { clientId } = await masterDb.get(ctx.user.appId) if (!clientId) { ctx.throw(400, "ClientId not suplied") } // find the instance that the user is associated with const db = new CouchDB(ClientDb.name(clientId)) - const appId = ctx.params.appId - const app = await db.get(appId) + const app = await db.get(ctx.user.appId) const instanceId = app.userInstanceMap[username] if (!instanceId) - ctx.throw(500, "User is not associated with an instance of app", appId) + ctx.throw( + 500, + "User is not associated with an instance of app", + ctx.user.appId + ) // Check the user exists in the instance DB by username const instanceDb = new CouchDB(instanceId) @@ -41,16 +47,22 @@ exports.authenticate = async ctx => { const payload = { userId: dbUser._id, accessLevelId: dbUser.accessLevelId, - instanceId: instanceId, + appId: ctx.user.appId, + instanceId, } const token = jwt.sign(payload, ctx.config.jwtSecret, { expiresIn: "1 day", }) - const ONE_DAY_FROM_NOW = new Date(Date.now() + 24 * 3600) + const expires = new Date() + expires.setDate(expires.getDate() + 1) - ctx.cookies.set("budibase:token", token, { expires: ONE_DAY_FROM_NOW }) + ctx.cookies.set("budibase:token", token, { + expires, + path: "/", + httpOnly: false, + }) ctx.body = { token, diff --git a/packages/server/src/api/controllers/instance.js b/packages/server/src/api/controllers/instance.js index 155437371f..903d3ff651 100644 --- a/packages/server/src/api/controllers/instance.js +++ b/packages/server/src/api/controllers/instance.js @@ -4,19 +4,19 @@ const newid = require("../../db/newid") exports.create = async function(ctx) { const instanceName = ctx.request.body.name - const appShortId = ctx.params.applicationId.substring(0, 7) + const { appId } = ctx.user + const appShortId = appId.substring(0, 7) const instanceId = `inst_${appShortId}_${newid()}` - const { applicationId } = ctx.params const masterDb = new CouchDB("clientAppLookup") - const { clientId } = await masterDb.get(applicationId) + const { clientId } = await masterDb.get(appId) const db = new CouchDB(instanceId) await db.put({ _id: "_design/database", metadata: { clientId, - applicationId, + applicationId: appId, }, views: { by_username: { @@ -46,7 +46,7 @@ exports.create = async function(ctx) { // Add the new instance under the app clientDB const clientDb = new CouchDB(client.name(clientId)) - const budibaseApp = await clientDb.get(applicationId) + const budibaseApp = await clientDb.get(appId) const instance = { _id: instanceId, name: instanceName } budibaseApp.instances.push(instance) await clientDb.put(budibaseApp) diff --git a/packages/server/src/api/controllers/model.js b/packages/server/src/api/controllers/model.js index 650342b33c..e7792195cb 100644 --- a/packages/server/src/api/controllers/model.js +++ b/packages/server/src/api/controllers/model.js @@ -2,7 +2,7 @@ const CouchDB = require("../../db") const newid = require("../../db/newid") exports.fetch = async function(ctx) { - const db = new CouchDB(ctx.params.instanceId) + const db = new CouchDB(ctx.user.instanceId) const body = await db.query("database/by_type", { include_docs: true, key: ["model"], @@ -11,13 +11,13 @@ exports.fetch = async function(ctx) { } exports.find = async function(ctx) { - const db = new CouchDB(ctx.params.instanceId) + const db = new CouchDB(ctx.user.instanceId) const model = await db.get(ctx.params.id) ctx.body = model } exports.create = async function(ctx) { - const db = new CouchDB(ctx.params.instanceId) + const db = new CouchDB(ctx.user.instanceId) const newModel = { type: "model", ...ctx.request.body, @@ -65,7 +65,7 @@ exports.create = async function(ctx) { exports.update = async function() {} exports.destroy = async function(ctx) { - const db = new CouchDB(ctx.params.instanceId) + const db = new CouchDB(ctx.user.instanceId) const modelToDelete = await db.get(ctx.params.modelId) diff --git a/packages/server/src/api/controllers/record.js b/packages/server/src/api/controllers/record.js index cf8eb27606..4e4af81e01 100644 --- a/packages/server/src/api/controllers/record.js +++ b/packages/server/src/api/controllers/record.js @@ -3,7 +3,7 @@ const validateJs = require("validate.js") const newid = require("../../db/newid") exports.save = async function(ctx) { - const db = new CouchDB(ctx.params.instanceId) + const db = new CouchDB(ctx.user.instanceId) const record = ctx.request.body record.modelId = ctx.params.modelId @@ -46,7 +46,7 @@ exports.save = async function(ctx) { ctx.eventEmitter && ctx.eventEmitter.emit(`record:save`, { record, - instanceId: ctx.params.instanceId, + instanceId: ctx.user.instanceId, }) ctx.body = record ctx.status = 200 @@ -54,7 +54,7 @@ exports.save = async function(ctx) { } exports.fetchView = async function(ctx) { - const db = new CouchDB(ctx.params.instanceId) + const db = new CouchDB(ctx.user.instanceId) const response = await db.query(`database/${ctx.params.viewName}`, { include_docs: true, }) @@ -62,7 +62,7 @@ exports.fetchView = async function(ctx) { } exports.fetchModelRecords = async function(ctx) { - const db = new CouchDB(ctx.params.instanceId) + const db = new CouchDB(ctx.user.instanceId) const response = await db.query(`database/all_${ctx.params.modelId}`, { include_docs: true, }) @@ -70,7 +70,7 @@ exports.fetchModelRecords = async function(ctx) { } exports.search = async function(ctx) { - const db = new CouchDB(ctx.params.instanceId) + const db = new CouchDB(ctx.user.instanceId) const response = await db.allDocs({ include_docs: true, ...ctx.request.body, @@ -79,7 +79,7 @@ exports.search = async function(ctx) { } exports.find = async function(ctx) { - const db = new CouchDB(ctx.params.instanceId) + const db = new CouchDB(ctx.user.instanceId) const record = await db.get(ctx.params.recordId) if (record.modelId !== ctx.params.modelId) { ctx.throw(400, "Supplied modelId doe not match the record's modelId") @@ -89,7 +89,7 @@ exports.find = async function(ctx) { } exports.destroy = async function(ctx) { - const db = new CouchDB(ctx.params.instanceId) + const db = new CouchDB(ctx.user.instanceId) const record = await db.get(ctx.params.recordId) if (record.modelId !== ctx.params.modelId) { ctx.throw(400, "Supplied modelId doe not match the record's modelId") @@ -101,7 +101,7 @@ exports.destroy = async function(ctx) { exports.validate = async function(ctx) { const errors = await validate({ - instanceId: ctx.params.instanceId, + instanceId: ctx.user.instanceId, modelId: ctx.params.modelId, record: ctx.request.body, }) diff --git a/packages/server/src/api/controllers/static.js b/packages/server/src/api/controllers/static.js index 557a9efe7a..67ec8e391a 100644 --- a/packages/server/src/api/controllers/static.js +++ b/packages/server/src/api/controllers/static.js @@ -4,11 +4,15 @@ const { budibaseAppsDir, budibaseTempDir, } = require("../../utilities/budibaseDir") -const env = require("../../environment") +const setBuilderToken = require("../../utilities/builder/setBuilderToken") +const { ANON_LEVEL_ID } = require("../../utilities/accessLevels") +const jwt = require("jsonwebtoken") exports.serveBuilder = async function(ctx) { let builderPath = resolve(__dirname, "../../../builder") - ctx.cookies.set("builder:token", env.ADMIN_SECRET) + if (ctx.file === "index.html") { + setBuilderToken(ctx) + } await send(ctx, ctx.file, { root: ctx.devPath || builderPath }) } @@ -20,6 +24,33 @@ exports.serveApp = async function(ctx) { "public", ctx.isAuthenticated ? "main" : "unauthenticated" ) + // only set the appId cookie for /appId .. we COULD check for valid appIds + // but would like to avoid that DB hit + const looksLikeAppId = /^[0-9a-f]{32}$/.test(ctx.params.appId) + if (looksLikeAppId && !ctx.isAuthenticated) { + const anonUser = { + userId: "ANON", + accessLevelId: ANON_LEVEL_ID, + appId: ctx.params.appId, + } + const anonToken = jwt.sign(anonUser, ctx.config.jwtSecret) + ctx.cookies.set("budibase:token", anonToken, { + path: "/", + httpOnly: false, + }) + } + + await send(ctx, ctx.file || "index.html", { root: ctx.devPath || appPath }) +} + +exports.serveAppAsset = async function(ctx) { + // default to homedir + const appPath = resolve( + budibaseAppsDir(), + ctx.user.appId, + "public", + ctx.isAuthenticated ? "main" : "unauthenticated" + ) await send(ctx, ctx.file, { root: ctx.devPath || appPath }) } @@ -28,7 +59,7 @@ exports.serveComponentLibrary = async function(ctx) { // default to homedir let componentLibraryPath = resolve( budibaseAppsDir(), - ctx.params.appId, + ctx.user.appId, "node_modules", decodeURI(ctx.query.library), "dist" diff --git a/packages/server/src/api/controllers/user.js b/packages/server/src/api/controllers/user.js index 1b66cf1cca..71816f1c78 100644 --- a/packages/server/src/api/controllers/user.js +++ b/packages/server/src/api/controllers/user.js @@ -8,7 +8,7 @@ const { } = require("../../utilities/accessLevels") exports.fetch = async function(ctx) { - const database = new CouchDB(ctx.params.instanceId) + const database = new CouchDB(ctx.user.instanceId) const data = await database.query("database/by_type", { include_docs: true, key: ["user"], @@ -18,7 +18,7 @@ exports.fetch = async function(ctx) { } exports.create = async function(ctx) { - const database = new CouchDB(ctx.params.instanceId) + const database = new CouchDB(ctx.user.instanceId) const appId = (await database.get("_design/database")).metadata.applicationId const { username, password, name, accessLevelId } = ctx.request.body @@ -50,7 +50,7 @@ exports.create = async function(ctx) { app.userInstanceMap = { ...app.userInstanceMap, - [username]: ctx.params.instanceId, + [username]: ctx.user.instanceId, } await db.put(app) @@ -66,14 +66,14 @@ exports.create = async function(ctx) { exports.update = async function() {} exports.destroy = async function(ctx) { - const database = new CouchDB(ctx.params.instanceId) + const database = new CouchDB(ctx.user.instanceId) await database.destroy(getUserId(ctx.params.username)) ctx.message = `User ${ctx.params.username} deleted.` ctx.status = 200 } exports.find = async function(ctx) { - const database = new CouchDB(ctx.params.instanceId) + const database = new CouchDB(ctx.user.instanceId) const user = await database.get(getUserId(ctx.params.username)) ctx.body = { username: user.username, diff --git a/packages/server/src/api/controllers/view.js b/packages/server/src/api/controllers/view.js index 9e847da358..d449ae36f9 100644 --- a/packages/server/src/api/controllers/view.js +++ b/packages/server/src/api/controllers/view.js @@ -3,7 +3,7 @@ const CouchDB = require("../../db") const controller = { query: async () => {}, fetch: async ctx => { - const db = new CouchDB(ctx.params.instanceId) + const db = new CouchDB(ctx.user.instanceId) const designDoc = await db.get("_design/database") const response = [] @@ -24,7 +24,7 @@ const controller = { ctx.body = response }, create: async ctx => { - const db = new CouchDB(ctx.params.instanceId) + const db = new CouchDB(ctx.user.instanceId) const newView = ctx.request.body const designDoc = await db.get("_design/database") @@ -38,7 +38,7 @@ const controller = { ctx.message = `View ${newView.name} created successfully.` }, destroy: async ctx => { - const db = new CouchDB(ctx.params.instanceId) + const db = new CouchDB(ctx.user.instanceId) ctx.body = await db.destroy(ctx.params.userId) }, } diff --git a/packages/server/src/api/controllers/workflow/index.js b/packages/server/src/api/controllers/workflow/index.js index f1dd1bcb89..5c77bbfaa6 100644 --- a/packages/server/src/api/controllers/workflow/index.js +++ b/packages/server/src/api/controllers/workflow/index.js @@ -2,7 +2,7 @@ const CouchDB = require("../../../db") const newid = require("../../../db/newid") exports.create = async function(ctx) { - const db = new CouchDB(ctx.params.instanceId) + const db = new CouchDB(ctx.user.instanceId) const workflow = ctx.request.body workflow._id = newid() @@ -22,7 +22,7 @@ exports.create = async function(ctx) { } exports.update = async function(ctx) { - const db = new CouchDB(ctx.params.instanceId) + const db = new CouchDB(ctx.user.instanceId) const workflow = ctx.request.body const response = await db.put(workflow) @@ -40,7 +40,7 @@ exports.update = async function(ctx) { } exports.fetch = async function(ctx) { - const db = new CouchDB(ctx.params.instanceId) + const db = new CouchDB(ctx.user.instanceId) const response = await db.query(`database/by_type`, { key: ["workflow"], include_docs: true, @@ -69,6 +69,6 @@ exports.fetchActionScript = async function(ctx) { } exports.destroy = async function(ctx) { - const db = new CouchDB(ctx.params.instanceId) + const db = new CouchDB(ctx.user.instanceId) ctx.body = await db.remove(ctx.params.id, ctx.params.rev) } diff --git a/packages/server/src/api/routes/accesslevel.js b/packages/server/src/api/routes/accesslevel.js index d34acab02c..af1ff80ec6 100644 --- a/packages/server/src/api/routes/accesslevel.js +++ b/packages/server/src/api/routes/accesslevel.js @@ -4,11 +4,11 @@ const controller = require("../controllers/accesslevel") const router = Router() router - .post("/api/:instanceId/accesslevels", controller.create) - .put("/api/:instanceId/accesslevels", controller.update) - .get("/api/:instanceId/accesslevels", controller.fetch) - .get("/api/:instanceId/accesslevels/:levelId", controller.find) - .delete("/api/:instanceId/accesslevels/:levelId/:rev", controller.destroy) - .patch("/api/:instanceId/accesslevels/:levelId", controller.patch) + .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) module.exports = router diff --git a/packages/server/src/api/routes/auth.js b/packages/server/src/api/routes/auth.js index fa95a3a5e6..b4b68e8929 100644 --- a/packages/server/src/api/routes/auth.js +++ b/packages/server/src/api/routes/auth.js @@ -3,6 +3,6 @@ const controller = require("../controllers/auth") const router = Router() -router.post("/:appId/api/authenticate", controller.authenticate) +router.post("/api/authenticate", controller.authenticate) module.exports = router diff --git a/packages/server/src/api/routes/instance.js b/packages/server/src/api/routes/instance.js index 9b7b3db511..00fce6bb12 100644 --- a/packages/server/src/api/routes/instance.js +++ b/packages/server/src/api/routes/instance.js @@ -6,7 +6,7 @@ const { BUILDER } = require("../../utilities/accessLevels") const router = Router() router - .post("/api/:applicationId/instances", authorized(BUILDER), controller.create) + .post("/api/instances", authorized(BUILDER), controller.create) .delete("/api/instances/:instanceId", authorized(BUILDER), controller.destroy) module.exports = router diff --git a/packages/server/src/api/routes/model.js b/packages/server/src/api/routes/model.js index f1ec46dbe5..10882aa057 100644 --- a/packages/server/src/api/routes/model.js +++ b/packages/server/src/api/routes/model.js @@ -1,17 +1,21 @@ const Router = require("@koa/router") const modelController = require("../controllers/model") const authorized = require("../../middleware/authorized") -const { BUILDER } = require("../../utilities/accessLevels") +const { BUILDER, READ_MODEL } = require("../../utilities/accessLevels") const router = Router() router - .get("/api/:instanceId/models", authorized(BUILDER), modelController.fetch) - .get("/api/:instanceId/models/:id", authorized(BUILDER), modelController.find) - .post("/api/:instanceId/models", authorized(BUILDER), modelController.create) + .get("/api/models", authorized(BUILDER), modelController.fetch) + .get( + "/api/models/:id", + authorized(READ_MODEL, ctx => ctx.params.id), + modelController.find + ) + .post("/api/models", authorized(BUILDER), modelController.create) // .patch("/api/:instanceId/models", controller.update) .delete( - "/api/:instanceId/models/:modelId/:revId", + "/api/models/:modelId/:revId", authorized(BUILDER), modelController.destroy ) diff --git a/packages/server/src/api/routes/record.js b/packages/server/src/api/routes/record.js index d555d3d8c8..deba513aa9 100644 --- a/packages/server/src/api/routes/record.js +++ b/packages/server/src/api/routes/record.js @@ -7,28 +7,28 @@ const router = Router() router .get( - "/api/:instanceId/:modelId/records", + "/api/:modelId/records", authorized(READ_MODEL, ctx => ctx.params.modelId), recordController.fetchModelRecords ) .get( - "/api/:instanceId/:modelId/records/:recordId", + "/api/:modelId/records/:recordId", authorized(READ_MODEL, ctx => ctx.params.modelId), recordController.find ) - .post("/api/:instanceId/records/search", recordController.search) + .post("/api/records/search", recordController.search) .post( - "/api/:instanceId/:modelId/records", + "/api/:modelId/records", authorized(WRITE_MODEL, ctx => ctx.params.modelId), recordController.save ) .post( - "/api/:instanceId/:modelId/records/validate", + "/api/:modelId/records/validate", authorized(WRITE_MODEL, ctx => ctx.params.modelId), recordController.validate ) .delete( - "/api/:instanceId/:modelId/records/:recordId/:revId", + "/api/:modelId/records/:recordId/:revId", authorized(WRITE_MODEL, ctx => ctx.params.modelId), recordController.destroy ) diff --git a/packages/server/src/api/routes/screen.js b/packages/server/src/api/routes/screen.js index 19823aab68..60e29bf363 100644 --- a/packages/server/src/api/routes/screen.js +++ b/packages/server/src/api/routes/screen.js @@ -6,12 +6,8 @@ const { BUILDER } = require("../../utilities/accessLevels") const router = Router() router - .get("/api/:instanceId/screens", authorized(BUILDER), controller.fetch) - .post("/api/:instanceId/screens", authorized(BUILDER), controller.save) - .delete( - "/api/:instanceId/:screenId/:revId", - authorized(BUILDER), - controller.destroy - ) + .get("/api/screens", authorized(BUILDER), controller.fetch) + .post("/api/screens", authorized(BUILDER), controller.save) + .delete("/api/:screenId/:revId", authorized(BUILDER), controller.destroy) module.exports = router diff --git a/packages/server/src/api/routes/static.js b/packages/server/src/api/routes/static.js index 5909d31d09..6fff1996fa 100644 --- a/packages/server/src/api/routes/static.js +++ b/packages/server/src/api/routes/static.js @@ -21,7 +21,8 @@ if (env.NODE_ENV !== "production") { } router - .get("/:appId/componentlibrary", controller.serveComponentLibrary) - .get("/:appId/:file*", controller.serveApp) + .get("/componentlibrary", controller.serveComponentLibrary) + .get("/assets/:file*", controller.serveAppAsset) + .get("/:appId/:path*", controller.serveApp) 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 ef2d1575cd..81ab6a78ad 100644 --- a/packages/server/src/api/routes/tests/accesslevel.spec.js +++ b/packages/server/src/api/routes/tests/accesslevel.spec.js @@ -36,17 +36,17 @@ describe("/accesslevels", () => { beforeEach(async () => { instanceId = (await createInstance(request, appId))._id - model = await createModel(request, instanceId) - view = await createView(request, instanceId) + model = await createModel(request, appId, instanceId) + view = await createView(request, appId, instanceId) }) describe("create", () => { it("returns a success message when level is successfully created", async () => { const res = await request - .post(`/api/${instanceId}/accesslevels`) + .post(`/api/accesslevels`) .send({ name: "user" }) - .set(defaultHeaders) + .set(defaultHeaders(appId, instanceId)) .expect('Content-Type', /json/) .expect(200) @@ -62,17 +62,17 @@ describe("/accesslevels", () => { it("should list custom levels, plus 2 default levels", async () => { const createRes = await request - .post(`/api/${instanceId}/accesslevels`) + .post(`/api/accesslevels`) .send({ name: "user", permissions: [ { itemId: model._id, name: READ_MODEL }] }) - .set(defaultHeaders) + .set(defaultHeaders(appId, instanceId)) .expect('Content-Type', /json/) .expect(200) const customLevel = createRes.body const res = await request - .get(`/api/${instanceId}/accesslevels`) - .set(defaultHeaders) + .get(`/api/accesslevels`) + .set(defaultHeaders(appId, instanceId)) .expect('Content-Type', /json/) .expect(200) @@ -95,22 +95,22 @@ describe("/accesslevels", () => { describe("destroy", () => { it("should delete custom access level", async () => { const createRes = await request - .post(`/api/${instanceId}/accesslevels`) + .post(`/api/accesslevels`) .send({ name: "user", permissions: [ { itemId: model._id, name: READ_MODEL } ] }) - .set(defaultHeaders) + .set(defaultHeaders(appId, instanceId)) .expect('Content-Type', /json/) .expect(200) const customLevel = createRes.body await request - .delete(`/api/${instanceId}/accesslevels/${customLevel._id}/${customLevel._rev}`) - .set(defaultHeaders) + .delete(`/api/accesslevels/${customLevel._id}/${customLevel._rev}`) + .set(defaultHeaders(appId, instanceId)) .expect(200) await request - .get(`/api/${instanceId}/accesslevels/${customLevel._id}`) - .set(defaultHeaders) + .get(`/api/accesslevels/${customLevel._id}`) + .set(defaultHeaders(appId, instanceId)) .expect(404) }) }) @@ -118,27 +118,27 @@ describe("/accesslevels", () => { describe("patch", () => { it("should add given permissions", async () => { const createRes = await request - .post(`/api/${instanceId}/accesslevels`) + .post(`/api/accesslevels`) .send({ name: "user", permissions: [ { itemId: model._id, name: READ_MODEL }] }) - .set(defaultHeaders) + .set(defaultHeaders(appId, instanceId)) .expect('Content-Type', /json/) .expect(200) const customLevel = createRes.body await request - .patch(`/api/${instanceId}/accesslevels/${customLevel._id}`) + .patch(`/api/accesslevels/${customLevel._id}`) .send({ _rev: customLevel._rev, addedPermissions: [ { itemId: model._id, name: WRITE_MODEL } ] }) - .set(defaultHeaders) + .set(defaultHeaders(appId, instanceId)) .expect('Content-Type', /json/) .expect(200) const finalRes = await request - .get(`/api/${instanceId}/accesslevels/${customLevel._id}`) - .set(defaultHeaders) + .get(`/api/accesslevels/${customLevel._id}`) + .set(defaultHeaders(appId, instanceId)) .expect(200) expect(finalRes.body.permissions.length).toBe(2) @@ -148,7 +148,7 @@ describe("/accesslevels", () => { it("should remove given permissions", async () => { const createRes = await request - .post(`/api/${instanceId}/accesslevels`) + .post(`/api/accesslevels`) .send({ name: "user", permissions: [ @@ -156,25 +156,25 @@ describe("/accesslevels", () => { { itemId: model._id, name: WRITE_MODEL }, ] }) - .set(defaultHeaders) + .set(defaultHeaders(appId, instanceId)) .expect('Content-Type', /json/) .expect(200) const customLevel = createRes.body await request - .patch(`/api/${instanceId}/accesslevels/${customLevel._id}`) + .patch(`/api/accesslevels/${customLevel._id}`) .send({ _rev: customLevel._rev, removedPermissions: [ { itemId: model._id, name: WRITE_MODEL }] }) - .set(defaultHeaders) + .set(defaultHeaders(appId, instanceId)) .expect('Content-Type', /json/) .expect(200) const finalRes = await request - .get(`/api/${instanceId}/accesslevels/${customLevel._id}`) - .set(defaultHeaders) + .get(`/api/accesslevels/${customLevel._id}`) + .set(defaultHeaders(appId, instanceId)) .expect(200) expect(finalRes.body.permissions.length).toBe(1) diff --git a/packages/server/src/api/routes/tests/application.spec.js b/packages/server/src/api/routes/tests/application.spec.js index 7f64419778..d22ab02016 100644 --- a/packages/server/src/api/routes/tests/application.spec.js +++ b/packages/server/src/api/routes/tests/application.spec.js @@ -34,7 +34,7 @@ describe("/applications", () => { const res = await request .post("/api/applications") .send({ name: "My App" }) - .set(defaultHeaders) + .set(defaultHeaders()) .expect('Content-Type', /json/) .expect(200) expect(res.res.statusMessage).toEqual("Application My App created successfully") @@ -49,6 +49,7 @@ describe("/applications", () => { method: "POST", url: `/api/applications`, instanceId: instance._id, + appId: otherApplication._id, body: { name: "My App" } }) }) @@ -63,7 +64,7 @@ describe("/applications", () => { const res = await request .get("/api/applications") - .set(defaultHeaders) + .set(defaultHeaders()) .expect('Content-Type', /json/) .expect(200) @@ -77,13 +78,13 @@ describe("/applications", () => { const blah = await request .post("/api/applications") .send({ name: "app2", clientId: "new_client"}) - .set(defaultHeaders) + .set(defaultHeaders()) .expect('Content-Type', /json/) //.expect(200) const client1Res = await request .get(`/api/applications?clientId=${TEST_CLIENT_ID}`) - .set(defaultHeaders) + .set(defaultHeaders()) .expect('Content-Type', /json/) .expect(200) @@ -92,7 +93,7 @@ describe("/applications", () => { const client2Res = await request .get(`/api/applications?clientId=new_client`) - .set(defaultHeaders) + .set(defaultHeaders()) .expect('Content-Type', /json/) .expect(200) @@ -109,6 +110,7 @@ describe("/applications", () => { method: "GET", url: `/api/applications`, instanceId: instance._id, + appId: otherApplication._id, }) }) }) diff --git a/packages/server/src/api/routes/tests/couchTestUtils.js b/packages/server/src/api/routes/tests/couchTestUtils.js index 495b841b10..17f096ec6b 100644 --- a/packages/server/src/api/routes/tests/couchTestUtils.js +++ b/packages/server/src/api/routes/tests/couchTestUtils.js @@ -4,8 +4,12 @@ const supertest = require("supertest") const app = require("../../../app") const { POWERUSER_LEVEL_ID, + ANON_LEVEL_ID, + BUILDER_LEVEL_ID, generateAdminPermissions, } = require("../../../utilities/accessLevels") +const jwt = require("jsonwebtoken") +const env = require("../../../environment") const TEST_CLIENT_ID = "test-client-id" @@ -20,13 +24,23 @@ exports.supertest = async () => { return { request, server } } -exports.defaultHeaders = { - Accept: "application/json", - Cookie: ["builder:token=test-admin-secret"], - "x-user-agent": "Budibase Builder", +exports.defaultHeaders = (appId, instanceId) => { + const builderUser = { + userId: "BUILDER", + accessLevelId: BUILDER_LEVEL_ID, + appId, + instanceId, + } + + const builderToken = jwt.sign(builderUser, env.JWT_SECRET) + + return { + Accept: "application/json", + Cookie: [`builder:token=${builderToken}`], + } } -exports.createModel = async (request, instanceId, model) => { +exports.createModel = async (request, appId, instanceId, model) => { model = model || { name: "TestModel", type: "model", @@ -42,20 +56,20 @@ exports.createModel = async (request, instanceId, model) => { } const res = await request - .post(`/api/${instanceId}/models`) - .set(exports.defaultHeaders) + .post(`/api/models`) + .set(exports.defaultHeaders(appId, instanceId)) .send(model) return res.body } -exports.createView = async (request, instanceId, view) => { +exports.createView = async (request, appId, instanceId, view) => { view = view || { map: "function(doc) { emit(doc[doc.key], doc._id); } ", } const res = await request - .post(`/api/${instanceId}/views`) - .set(exports.defaultHeaders) + .post(`/api/views`) + .set(exports.defaultHeaders(appId, instanceId)) .send(view) return res.body } @@ -65,10 +79,10 @@ exports.createClientDatabase = async id => await create(id || TEST_CLIENT_ID) exports.createApplication = async (request, name = "test_application") => { const res = await request .post("/api/applications") - .set(exports.defaultHeaders) .send({ name, }) + .set(exports.defaultHeaders()) return res.body } @@ -76,23 +90,24 @@ exports.destroyClientDatabase = async () => await destroy(TEST_CLIENT_ID) exports.createInstance = async (request, appId) => { const res = await request - .post(`/api/${appId}/instances`) - .set(exports.defaultHeaders) + .post(`/api/instances`) .send({ - name: "test-instance", + name: "test-instance2", }) + .set(exports.defaultHeaders(appId)) return res.body } exports.createUser = async ( request, + appId, instanceId, username = "babs", password = "babs_password" ) => { const res = await request - .post(`/api/${instanceId}/users`) - .set(exports.defaultHeaders) + .post(`/api/users`) + .set(exports.defaultHeaders(appId, instanceId)) .send({ name: "Bill", username, @@ -104,6 +119,7 @@ exports.createUser = async ( const createUserWithOnePermission = async ( request, + appId, instanceId, permName, itemId @@ -115,17 +131,19 @@ const createUserWithOnePermission = async ( return await createUserWithPermissions( request, + appId, instanceId, permissions, "onePermOnlyUser" ) } -const createUserWithAdminPermissions = async (request, instanceId) => { +const createUserWithAdminPermissions = async (request, appId, instanceId) => { let permissions = await generateAdminPermissions(instanceId) return await createUserWithPermissions( request, + appId, instanceId, permissions, "adminUser" @@ -134,6 +152,7 @@ const createUserWithAdminPermissions = async (request, instanceId) => { const createUserWithAllPermissionExceptOne = async ( request, + appId, instanceId, permName, itemId @@ -145,6 +164,7 @@ const createUserWithAllPermissionExceptOne = async ( return await createUserWithPermissions( request, + appId, instanceId, permissions, "allPermsExceptOneUser" @@ -153,19 +173,20 @@ const createUserWithAllPermissionExceptOne = async ( const createUserWithPermissions = async ( request, + appId, instanceId, permissions, username ) => { const accessRes = await request - .post(`/api/${instanceId}/accesslevels`) + .post(`/api/accesslevels`) .send({ name: "TestLevel", permissions }) - .set(exports.defaultHeaders) + .set(exports.defaultHeaders(appId, instanceId)) const password = `password_${username}` await request - .post(`/api/${instanceId}/users`) - .set(exports.defaultHeaders) + .post(`/api/users`) + .set(exports.defaultHeaders(appId, instanceId)) .send({ name: username, username, @@ -173,11 +194,20 @@ const createUserWithPermissions = async ( accessLevelId: accessRes.body._id, }) - const db = new CouchDB(instanceId) - const designDoc = await db.get("_design/database") + //const db = new CouchDB(instanceId) + //const designDoc = await db.get("_design/database") + + const anonUser = { + userId: "ANON", + accessLevelId: ANON_LEVEL_ID, + appId: appId, + } + + const anonToken = jwt.sign(anonUser, env.JWT_SECRET) const loginResult = await request - .post(`/${designDoc.metadata.applicationId}/api/authenticate`) + .post(`/api/authenticate`) + .set({ Cookie: `budibase:token=${anonToken}` }) .send({ username, password }) // returning necessary request headers @@ -192,12 +222,14 @@ exports.testPermissionsForEndpoint = async ({ method, url, body, + appId, instanceId, permissionName, itemId, }) => { const headers = await createUserWithOnePermission( request, + appId, instanceId, permissionName, itemId @@ -209,6 +241,7 @@ exports.testPermissionsForEndpoint = async ({ const noPermsHeaders = await createUserWithAllPermissionExceptOne( request, + appId, instanceId, permissionName, itemId @@ -224,9 +257,14 @@ exports.builderEndpointShouldBlockNormalUsers = async ({ method, url, body, + appId, instanceId, }) => { - const headers = await createUserWithAdminPermissions(request, instanceId) + const headers = await createUserWithAdminPermissions( + request, + appId, + instanceId + ) await createRequest(request, method, url, body) .set(headers) diff --git a/packages/server/src/api/routes/tests/instance.spec.js b/packages/server/src/api/routes/tests/instance.spec.js index 0b8bd44cb6..3bbed4d8e6 100644 --- a/packages/server/src/api/routes/tests/instance.spec.js +++ b/packages/server/src/api/routes/tests/instance.spec.js @@ -24,9 +24,9 @@ describe("/instances", () => { it("returns a success message when the instance database is successfully created", async () => { const res = await request - .post(`/api/${TEST_APP_ID}/instances`) + .post(`/api/instances`) .send({ name: "test-instance" }) - .set(defaultHeaders) + .set(defaultHeaders(TEST_APP_ID)) .expect('Content-Type', /json/) .expect(200) @@ -42,7 +42,7 @@ describe("/instances", () => { const instance = await createInstance(request, TEST_APP_ID); const res = await request .delete(`/api/instances/${instance._id}`) - .set(defaultHeaders) + .set(defaultHeaders(TEST_APP_ID)) .expect(200) expect(res.res.statusMessage).toEqual(`Instance Database ${instance._id} successfully destroyed.`); diff --git a/packages/server/src/api/routes/tests/model.spec.js b/packages/server/src/api/routes/tests/model.spec.js index 7134245fb3..4d4e7aeb08 100644 --- a/packages/server/src/api/routes/tests/model.spec.js +++ b/packages/server/src/api/routes/tests/model.spec.js @@ -33,7 +33,7 @@ describe("/models", () => { it("returns a success message when the model is successfully created", done => { request - .post(`/api/${instance._id}/models`) + .post(`/api/models`) .send({ name: "TestModel", key: "name", @@ -41,7 +41,7 @@ describe("/models", () => { name: { type: "string" } } }) - .set(defaultHeaders) + .set(defaultHeaders(app._id, instance._id)) .expect('Content-Type', /json/) .expect(200) .end(async (err, res) => { @@ -55,8 +55,9 @@ describe("/models", () => { await builderEndpointShouldBlockNormalUsers({ request, method: "POST", - url: `/api/${instance._id}/models`, + url: `/api/models`, instanceId: instance._id, + appId: app._id, body: { name: "TestModel", key: "name", @@ -73,13 +74,13 @@ describe("/models", () => { beforeEach(async () => { instance = await createInstance(request, app._id) - testModel = await createModel(request, instance._id, testModel) + testModel = await createModel(request, app._id, instance._id, testModel) }); it("returns all the models for that instance in the response body", done => { request - .get(`/api/${instance._id}/models`) - .set(defaultHeaders) + .get(`/api/models`) + .set(defaultHeaders(app._id, instance._id)) .expect('Content-Type', /json/) .expect(200) .end(async (_, res) => { @@ -94,8 +95,9 @@ describe("/models", () => { await builderEndpointShouldBlockNormalUsers({ request, method: "GET", - url: `/api/${instance._id}/models`, + url: `/api/models`, instanceId: instance._id, + appId: app._id, }) }) }); @@ -105,7 +107,7 @@ describe("/models", () => { beforeEach(async () => { instance = await createInstance(request, app._id) - testModel = await createModel(request, instance._id, testModel) + testModel = await createModel(request, app._id, instance._id, testModel) }); afterEach(() => { @@ -114,8 +116,8 @@ describe("/models", () => { it("returns a success response when a model is deleted.", async done => { request - .delete(`/api/${instance._id}/models/${testModel._id}/${testModel._rev}`) - .set(defaultHeaders) + .delete(`/api/models/${testModel._id}/${testModel._rev}`) + .set(defaultHeaders(app._id, instance._id)) .expect('Content-Type', /json/) .expect(200) .end(async (_, res) => { @@ -125,7 +127,7 @@ describe("/models", () => { }) it("deletes linked references to the model after deletion", async done => { - const linkedModel = await createModel(request, instance._id, { + const linkedModel = await createModel(request, app._id, instance._id, { name: "LinkedModel", type: "model", key: "name", @@ -147,8 +149,8 @@ describe("/models", () => { }) request - .delete(`/api/${instance._id}/models/${testModel._id}/${testModel._rev}`) - .set(defaultHeaders) + .delete(`/api/models/${testModel._id}/${testModel._rev}`) + .set(defaultHeaders(app._id, instance._id)) .expect('Content-Type', /json/) .expect(200) .end(async (_, res) => { @@ -163,8 +165,9 @@ describe("/models", () => { await builderEndpointShouldBlockNormalUsers({ request, method: "DELETE", - url: `/api/${instance._id}/models/${testModel._id}/${testModel._rev}`, + url: `/api/models/${testModel._id}/${testModel._rev}`, instanceId: instance._id, + appId: app._id, }) }) diff --git a/packages/server/src/api/routes/tests/record.spec.js b/packages/server/src/api/routes/tests/record.spec.js index 22ac67ecdc..62cf205b1c 100644 --- a/packages/server/src/api/routes/tests/record.spec.js +++ b/packages/server/src/api/routes/tests/record.spec.js @@ -27,7 +27,7 @@ describe("/records", () => { beforeEach(async () => { instance = await createInstance(request, app._id) - model = await createModel(request, instance._id) + model = await createModel(request, app._id, instance._id) record = { name: "Test Contact", status: "new", @@ -39,9 +39,9 @@ describe("/records", () => { const createRecord = async r => await request - .post(`/api/${instance._id}/${model._id}/records`) + .post(`/api/${model._id}/records`) .send(r || record) - .set(defaultHeaders) + .set(defaultHeaders(app._id, instance._id)) .expect('Content-Type', /json/) .expect(200) @@ -57,14 +57,14 @@ describe("/records", () => { const existing = rec.body const res = await request - .post(`/api/${instance._id}/${model._id}/records`) + .post(`/api/${model._id}/records`) .send({ _id: existing._id, _rev: existing._rev, modelId: model._id, name: "Updated Name", }) - .set(defaultHeaders) + .set(defaultHeaders(app._id, instance._id)) .expect('Content-Type', /json/) .expect(200) @@ -77,8 +77,8 @@ describe("/records", () => { const existing = rec.body const res = await request - .get(`/api/${instance._id}/${model._id}/records/${existing._id}`) - .set(defaultHeaders) + .get(`/api/${model._id}/records/${existing._id}`) + .set(defaultHeaders(app._id, instance._id)) .expect('Content-Type', /json/) .expect(200) @@ -100,8 +100,8 @@ describe("/records", () => { await createRecord(newRecord) const res = await request - .get(`/api/${instance._id}/${model._id}/records`) - .set(defaultHeaders) + .get(`/api/${model._id}/records`) + .set(defaultHeaders(app._id, instance._id)) .expect('Content-Type', /json/) .expect(200) @@ -122,8 +122,8 @@ describe("/records", () => { const recordIds = [record.body._id, secondRecord.body._id] const res = await request - .post(`/api/${instance._id}/records/search`) - .set(defaultHeaders) + .post(`/api/records/search`) + .set(defaultHeaders(app._id, instance._id)) .send({ keys: recordIds }) @@ -137,8 +137,8 @@ describe("/records", () => { it("load should return 404 when record does not exist", async () => { await createRecord() await request - .get(`/api/${instance._id}/${model._id}/records/not-a-valid-id`) - .set(defaultHeaders) + .get(`/api/${model._id}/records/not-a-valid-id`) + .set(defaultHeaders(app._id, instance._id)) .expect('Content-Type', /json/) .expect(404) }) @@ -147,9 +147,9 @@ describe("/records", () => { describe("validate", () => { it("should return no errors on valid record", async () => { const result = await request - .post(`/api/${instance._id}/${model._id}/records/validate`) + .post(`/api/${model._id}/records/validate`) .send({ name: "ivan" }) - .set(defaultHeaders) + .set(defaultHeaders(app._id, instance._id)) .expect('Content-Type', /json/) .expect(200) @@ -159,9 +159,9 @@ describe("/records", () => { it("should errors on invalid record", async () => { const result = await request - .post(`/api/${instance._id}/${model._id}/records/validate`) + .post(`/api/${model._id}/records/validate`) .send({ name: 1 }) - .set(defaultHeaders) + .set(defaultHeaders(app._id, instance._id)) .expect('Content-Type', /json/) .expect(200) diff --git a/packages/server/src/api/routes/tests/user.spec.js b/packages/server/src/api/routes/tests/user.spec.js index d950f03c3d..a17e6fe275 100644 --- a/packages/server/src/api/routes/tests/user.spec.js +++ b/packages/server/src/api/routes/tests/user.spec.js @@ -36,11 +36,11 @@ describe("/users", () => { describe("fetch", () => { it("returns a list of users from an instance db", async () => { - await createUser(request, instance._id, "brenda", "brendas_password") - await createUser(request, instance._id, "pam", "pam_password") + await createUser(request, app._id, instance._id, "brenda", "brendas_password") + await createUser(request, app._id, instance._id, "pam", "pam_password") const res = await request - .get(`/api/${instance._id}/users`) - .set(defaultHeaders) + .get(`/api/users`) + .set(defaultHeaders(app._id, instance._id)) .expect('Content-Type', /json/) .expect(200) @@ -50,12 +50,13 @@ describe("/users", () => { }) it("should apply authorization to endpoint", async () => { - await createUser(request, instance._id, "brenda", "brendas_password") + await createUser(request, app._id, instance._id, "brenda", "brendas_password") await testPermissionsForEndpoint({ request, method: "GET", - url: `/api/${instance._id}/users`, + url: `/api/users`, instanceId: instance._id, + appId: app._id, permissionName: LIST_USERS, }) }) @@ -66,8 +67,8 @@ describe("/users", () => { it("returns a success message when a user is successfully created", async () => { const res = await request - .post(`/api/${instance._id}/users`) - .set(defaultHeaders) + .post(`/api/users`) + .set(defaultHeaders(app._id, instance._id)) .send({ name: "Bill", username: "bill", password: "bills_password", accessLevelId: POWERUSER_LEVEL_ID }) .expect(200) .expect('Content-Type', /json/) @@ -81,8 +82,9 @@ describe("/users", () => { request, method: "POST", body: { name: "brandNewUser", username: "brandNewUser", password: "yeeooo", accessLevelId: POWERUSER_LEVEL_ID }, - url: `/api/${instance._id}/users`, + url: `/api/users`, instanceId: instance._id, + appId: app._id, permissionName: USER_MANAGEMENT, }) }) diff --git a/packages/server/src/api/routes/tests/view.spec.js b/packages/server/src/api/routes/tests/view.spec.js index b8fd47972a..5a889bed67 100644 --- a/packages/server/src/api/routes/tests/view.spec.js +++ b/packages/server/src/api/routes/tests/view.spec.js @@ -30,7 +30,7 @@ describe("/views", () => { const createView = async () => await request - .post(`/api/${instance._id}/views`) + .post(`/api/views`) .send({ name: "TestView", map: `function(doc) { @@ -40,7 +40,7 @@ describe("/views", () => { }`, reduce: `function(keys, values) { }` }) - .set(defaultHeaders) + .set(defaultHeaders(app._id, instance._id)) .expect('Content-Type', /json/) .expect(200) @@ -56,14 +56,14 @@ describe("/views", () => { describe("fetch", () => { beforeEach(async () => { - model = await createModel(request, instance._id); + model = await createModel(request, app._id, instance._id); }); it("should only return custom views", async () => { const view = await createView() const res = await request - .get(`/api/${instance._id}/views`) - .set(defaultHeaders) + .get(`/api/views`) + .set(defaultHeaders(app._id, instance._id)) .expect('Content-Type', /json/) .expect(200) expect(res.body.length).toBe(1) diff --git a/packages/server/src/api/routes/tests/workflow.spec.js b/packages/server/src/api/routes/tests/workflow.spec.js index 1f345698bf..f8b38c53ec 100644 --- a/packages/server/src/api/routes/tests/workflow.spec.js +++ b/packages/server/src/api/routes/tests/workflow.spec.js @@ -63,8 +63,8 @@ describe("/workflows", () => { describe("create", () => { it("returns a success message when the workflow is successfully created", async () => { const res = await request - .post(`/api/${instance._id}/workflows`) - .set(defaultHeaders) + .post(`/api/workflows`) + .set(defaultHeaders(app._id, instance._id)) .send(TEST_WORKFLOW) .expect('Content-Type', /json/) .expect(200) @@ -77,8 +77,9 @@ describe("/workflows", () => { await builderEndpointShouldBlockNormalUsers({ request, method: "POST", - url: `/api/${instance._id}/workflows`, + url: `/api/workflows`, instanceId: instance._id, + appId: app._id, body: TEST_WORKFLOW }) }) @@ -92,8 +93,8 @@ describe("/workflows", () => { workflow.name = "Updated Name"; const res = await request - .put(`/api/${instance._id}/workflows`) - .set(defaultHeaders) + .put(`/api/workflows`) + .set(defaultHeaders(app._id, instance._id)) .send(workflow) .expect('Content-Type', /json/) .expect(200) @@ -107,8 +108,8 @@ describe("/workflows", () => { it("return all the workflows for an instance", async () => { await createWorkflow(); const res = await request - .get(`/api/${instance._id}/workflows`) - .set(defaultHeaders) + .get(`/api/workflows`) + .set(defaultHeaders(app._id, instance._id)) .expect('Content-Type', /json/) .expect(200) @@ -119,8 +120,9 @@ describe("/workflows", () => { await builderEndpointShouldBlockNormalUsers({ request, method: "GET", - url: `/api/${instance._id}/workflows`, + url: `/api/workflows`, instanceId: instance._id, + appId: app._id, }) }) }) @@ -129,8 +131,8 @@ describe("/workflows", () => { it("deletes a workflow by its ID", async () => { await createWorkflow(); const res = await request - .delete(`/api/${instance._id}/workflows/${workflow.id}/${workflow.rev}`) - .set(defaultHeaders) + .delete(`/api/workflows/${workflow.id}/${workflow.rev}`) + .set(defaultHeaders(app._id, instance._id)) .expect('Content-Type', /json/) .expect(200) @@ -142,8 +144,9 @@ describe("/workflows", () => { await builderEndpointShouldBlockNormalUsers({ request, method: "DELETE", - url: `/api/${instance._id}/workflows/${workflow.id}/${workflow._rev}`, + url: `/api/workflows/${workflow.id}/${workflow._rev}`, instanceId: instance._id, + appId: app._id, }) }) }) diff --git a/packages/server/src/api/routes/user.js b/packages/server/src/api/routes/user.js index 20e17c7473..3c560d3f80 100644 --- a/packages/server/src/api/routes/user.js +++ b/packages/server/src/api/routes/user.js @@ -6,19 +6,11 @@ const { USER_MANAGEMENT, LIST_USERS } = require("../../utilities/accessLevels") const router = Router() router - .get("/api/:instanceId/users", authorized(LIST_USERS), controller.fetch) - .get( - "/api/:instanceId/users/:username", - authorized(USER_MANAGEMENT), - controller.find - ) - .post( - "/api/:instanceId/users", - authorized(USER_MANAGEMENT), - controller.create - ) + .get("/api/users", authorized(LIST_USERS), controller.fetch) + .get("/api/users/:username", authorized(USER_MANAGEMENT), controller.find) + .post("/api/users", authorized(USER_MANAGEMENT), controller.create) .delete( - "/api/:instanceId/users/:username", + "/api/users/:username", authorized(USER_MANAGEMENT), controller.destroy ) diff --git a/packages/server/src/api/routes/view.js b/packages/server/src/api/routes/view.js index 193ece1cdf..5a73eea22e 100644 --- a/packages/server/src/api/routes/view.js +++ b/packages/server/src/api/routes/view.js @@ -8,13 +8,13 @@ const router = Router() router .get( - "/api/:instanceId/views/:viewName", + "/api/views/:viewName", authorized(READ_VIEW, ctx => ctx.params.viewName), recordController.fetchView ) - .get("/api/:instanceId/views", authorized(BUILDER), viewController.fetch) + .get("/api/views", authorized(BUILDER), viewController.fetch) // .patch("/api/:databaseId/views", controller.update); // .delete("/api/:instanceId/views/:viewId/:revId", controller.destroy); - .post("/api/:instanceId/views", authorized(BUILDER), viewController.create) + .post("/api/views", authorized(BUILDER), viewController.create) module.exports = router diff --git a/packages/server/src/api/routes/workflow.js b/packages/server/src/api/routes/workflow.js index fcb1d6e182..987e18a60f 100644 --- a/packages/server/src/api/routes/workflow.js +++ b/packages/server/src/api/routes/workflow.js @@ -6,20 +6,16 @@ const { BUILDER } = require("../../utilities/accessLevels") const router = Router() router - .get("/api/:instanceId/workflows", authorized(BUILDER), controller.fetch) + .get("/api/workflows", authorized(BUILDER), controller.fetch) .get("/api/workflows/:id", authorized(BUILDER), controller.find) .get( - "/api/:instanceId/workflows/:id/:action", + "/api/workflows/:id/:action", authorized(BUILDER), controller.fetchActionScript ) - .put("/api/:instanceId/workflows", authorized(BUILDER), controller.update) - .post("/api/:instanceId/workflows", authorized(BUILDER), controller.create) + .put("/api/workflows", authorized(BUILDER), controller.update) + .post("/api/workflows", authorized(BUILDER), controller.create) .post("/api/workflows/action", controller.executeAction) - .delete( - "/api/:instanceId/workflows/:id/:rev", - authorized(BUILDER), - controller.destroy - ) + .delete("/api/workflows/:id/:rev", authorized(BUILDER), controller.destroy) module.exports = router diff --git a/packages/server/src/middleware/authenticated.js b/packages/server/src/middleware/authenticated.js index d0ce1e2f30..36e2776abe 100644 --- a/packages/server/src/middleware/authenticated.js +++ b/packages/server/src/middleware/authenticated.js @@ -1,10 +1,11 @@ const jwt = require("jsonwebtoken") const STATUS_CODES = require("../utilities/statusCodes") -const env = require("../environment") const accessLevelController = require("../api/controllers/accesslevel") const { ADMIN_LEVEL_ID, POWERUSER_LEVEL_ID, + BUILDER_LEVEL_ID, + ANON_LEVEL_ID, } = require("../utilities/accessLevels") module.exports = async (ctx, next) => { @@ -15,16 +16,21 @@ module.exports = async (ctx, next) => { const appToken = ctx.cookies.get("budibase:token") const builderToken = ctx.cookies.get("builder:token") - const isBuilderAgent = ctx.headers["x-user-agent"] === "Budibase Builder" - // all admin api access should auth with buildertoken and 'Budibase Builder user agent - const shouldAuthAsBuilder = isBuilderAgent && builderToken - - if (shouldAuthAsBuilder) { - const builderTokenValid = builderToken === env.ADMIN_SECRET - - ctx.isAuthenticated = builderTokenValid - ctx.isBuilder = builderTokenValid + if (builderToken) { + try { + const jwtPayload = jwt.verify(builderToken, ctx.config.jwtSecret) + ctx.isAuthenticated = jwtPayload.accessLevelId === BUILDER_LEVEL_ID + ctx.user = { + ...jwtPayload, + accessLevel: await getAccessLevel( + jwtPayload.instanceId, + jwtPayload.accessLevelId + ), + } + } catch (_) { + // empty: do nothing + } await next() return @@ -46,7 +52,7 @@ module.exports = async (ctx, next) => { jwtPayload.accessLevelId ), } - ctx.isAuthenticated = true + ctx.isAuthenticated = ctx.user.accessLevelId !== ANON_LEVEL_ID } catch (err) { ctx.throw(err.status || STATUS_CODES.FORBIDDEN, err.text) } @@ -57,7 +63,9 @@ module.exports = async (ctx, next) => { const getAccessLevel = async (instanceId, accessLevelId) => { if ( accessLevelId === POWERUSER_LEVEL_ID || - accessLevelId === ADMIN_LEVEL_ID + accessLevelId === ADMIN_LEVEL_ID || + accessLevelId === BUILDER_LEVEL_ID || + accessLevelId === ANON_LEVEL_ID ) { return { _id: accessLevelId, @@ -69,6 +77,8 @@ const getAccessLevel = async (instanceId, accessLevelId) => { const findAccessContext = { params: { levelId: accessLevelId, + }, + user: { instanceId, }, } diff --git a/packages/server/src/middleware/authorized.js b/packages/server/src/middleware/authorized.js index b07715af36..b452d63cf5 100644 --- a/packages/server/src/middleware/authorized.js +++ b/packages/server/src/middleware/authorized.js @@ -2,6 +2,7 @@ const { adminPermissions, ADMIN_LEVEL_ID, POWERUSER_LEVEL_ID, + BUILDER_LEVEL_ID, BUILDER, } = require("../utilities/accessLevels") @@ -10,7 +11,11 @@ module.exports = (permName, getItemId) => async (ctx, next) => { ctx.throw(403, "Session not authenticated") } - if (ctx.isBuilder) { + if (!ctx.user) { + ctx.throw(403, "User not found") + } + + if (ctx.user.accessLevel._id === BUILDER_LEVEL_ID) { await next() return } @@ -20,10 +25,6 @@ module.exports = (permName, getItemId) => async (ctx, next) => { return } - if (!ctx.user) { - ctx.throw(403, "User not found") - } - const permissionId = ({ name, itemId }) => name + (itemId ? `-${itemId}` : "") if (ctx.user.accessLevel._id === ADMIN_LEVEL_ID) { diff --git a/packages/server/src/utilities/accessLevels.js b/packages/server/src/utilities/accessLevels.js index 9fff76e531..50ae559d07 100644 --- a/packages/server/src/utilities/accessLevels.js +++ b/packages/server/src/utilities/accessLevels.js @@ -5,6 +5,8 @@ const workflowController = require("../api/controllers/workflow") // Access Level IDs const ADMIN_LEVEL_ID = "ADMIN" const POWERUSER_LEVEL_ID = "POWER_USER" +const BUILDER_LEVEL_ID = "BUILDER" +const ANON_LEVEL_ID = "ANON" // Permissions const READ_MODEL = "read-model" @@ -28,7 +30,7 @@ const generateAdminPermissions = async instanceId => [ const generatePowerUserPermissions = async instanceId => { const fetchModelsCtx = { - params: { + user: { instanceId, }, } @@ -36,7 +38,7 @@ const generatePowerUserPermissions = async instanceId => { const models = fetchModelsCtx.body const fetchViewsCtx = { - params: { + user: { instanceId, }, } @@ -44,7 +46,7 @@ const generatePowerUserPermissions = async instanceId => { const views = fetchViewsCtx.body const fetchWorkflowsCtx = { - params: { + user: { instanceId, }, } @@ -83,6 +85,8 @@ const generatePowerUserPermissions = async instanceId => { module.exports = { ADMIN_LEVEL_ID, POWERUSER_LEVEL_ID, + BUILDER_LEVEL_ID, + ANON_LEVEL_ID, READ_MODEL, WRITE_MODEL, READ_VIEW, @@ -90,6 +94,7 @@ module.exports = { USER_MANAGEMENT, BUILDER, LIST_USERS, + adminPermissions, generateAdminPermissions, generatePowerUserPermissions, } diff --git a/packages/server/src/utilities/builder/buildPage.js b/packages/server/src/utilities/builder/buildPage.js index 3d44cb8072..ea0c9cce25 100644 --- a/packages/server/src/utilities/builder/buildPage.js +++ b/packages/server/src/utilities/builder/buildPage.js @@ -45,18 +45,16 @@ const copyClientLib = async (appPath, pageName) => { const buildIndexHtml = async (config, appId, pageName, appPath, pkg) => { const appPublicPath = publicPath(appPath, pageName) - const appRootPath = rootPath(config, appId) const stylesheetUrl = s => s.startsWith("http") ? s : `/${rootPath(config, appId)}/${s}` const templateObj = { title: pkg.page.title || "Budibase App", - favicon: `${appRootPath}/${pkg.page.favicon || "/_shared/favicon.png"}`, + favicon: `${pkg.page.favicon || "/_shared/favicon.png"}`, stylesheets: (pkg.page.stylesheets || []).map(stylesheetUrl), screenStyles: pkg.screens.filter(s => s._css).map(s => s._css), pageStyle: pkg.page._css, - appRootPath, } const indexHtmlTemplate = await readFile( @@ -74,7 +72,6 @@ const buildIndexHtml = async (config, appId, pageName, appPath, pkg) => { const buildFrontendAppDefinition = async (config, appId, pageName, pkg) => { const appPath = appPackageFolder(config, appId) const appPublicPath = publicPath(appPath, pageName) - const appRootPath = rootPath(config, appId) const filename = join(appPublicPath, "clientFrontendDefinition.js") @@ -89,7 +86,6 @@ const buildFrontendAppDefinition = async (config, appId, pageName, pkg) => { } const clientUiDefinition = JSON.stringify({ - appRootPath: appRootPath, page: pkg.page, screens: pkg.screens, libraries: [ diff --git a/packages/server/src/utilities/builder/index.template.html b/packages/server/src/utilities/builder/index.template.html index 2f1f7ed87c..a21219de9b 100644 --- a/packages/server/src/utilities/builder/index.template.html +++ b/packages/server/src/utilities/builder/index.template.html @@ -24,15 +24,15 @@ {{ /each }} {{ each(options.screenStyles) }} - + {{ /each }} {{ if(options.pageStyle) }} - + {{ /if }} - - + + diff --git a/packages/server/src/utilities/builder/setBuilderToken.js b/packages/server/src/utilities/builder/setBuilderToken.js new file mode 100644 index 0000000000..12622d5522 --- /dev/null +++ b/packages/server/src/utilities/builder/setBuilderToken.js @@ -0,0 +1,19 @@ +const { BUILDER_LEVEL_ID } = require("../accessLevels") +const jwt = require("jsonwebtoken") + +module.exports = (ctx, appId, instanceId) => { + const builderUser = { + userId: "BUILDER", + accessLevelId: BUILDER_LEVEL_ID, + instanceId, + appId, + } + + const token = jwt.sign(builderUser, ctx.config.jwtSecret, { + expiresIn: "30 days", + }) + + var expiry = new Date() + expiry.setDate(expiry.getDate() + 30) + ctx.cookies.set("builder:token", token, { expires: expiry, httpOnly: false }) +} diff --git a/packages/standard-components/public/clientAppDefinition.js b/packages/standard-components/public/clientAppDefinition.js index be67dcf1a8..f77f59ec03 100644 --- a/packages/standard-components/public/clientAppDefinition.js +++ b/packages/standard-components/public/clientAppDefinition.js @@ -98,6 +98,5 @@ window["##BUDIBASE_APPDEFINITION##"] = { nodeId: 0, }, componentLibraries: ["budibase-standard-components"], - appRootPath: "/testApp2", props: {}, } diff --git a/packages/standard-components/src/DataChart.svelte b/packages/standard-components/src/DataChart.svelte index 3f7aafd8a8..bccfc6314a 100644 --- a/packages/standard-components/src/DataChart.svelte +++ b/packages/standard-components/src/DataChart.svelte @@ -8,7 +8,6 @@ fcRoot(FusionCharts, Charts, FusionTheme) export let _bb - export let _instanceId export let model export let type = "column2d" @@ -25,7 +24,7 @@ } async function fetchData() { - const FETCH_RECORDS_URL = `/api/${_instanceId}/views/all_${model}` + const FETCH_RECORDS_URL = `/api/views/all_${model}` const response = await _bb.api.get(FETCH_RECORDS_URL) if (response.status === 200) { const json = await response.json() diff --git a/packages/standard-components/src/DataForm.svelte b/packages/standard-components/src/DataForm.svelte index 6a9788458e..6188165aa7 100644 --- a/packages/standard-components/src/DataForm.svelte +++ b/packages/standard-components/src/DataForm.svelte @@ -2,7 +2,6 @@ import { onMount } from "svelte" export let _bb - export let _instanceId export let model let username @@ -21,19 +20,19 @@ $: fields = Object.keys(schema) async function fetchModel() { - const FETCH_MODEL_URL = `/api/${_instanceId}/models/${model}` + const FETCH_MODEL_URL = `/api/models/${model}` const response = await _bb.api.get(FETCH_MODEL_URL) modelDef = await response.json() schema = modelDef.schema } async function save() { - const SAVE_RECORD_URL = `/api/${_instanceId}/${model}/records` + const SAVE_RECORD_URL = `/api/${model}/records` const response = await _bb.api.post(SAVE_RECORD_URL, newModel) const json = await response.json() store.update(state => { - state[model._id] = [...state[model], json] + state[model] = state[model] ? [...state[model], json] : [json] return state }) } diff --git a/packages/standard-components/src/DataList.svelte b/packages/standard-components/src/DataList.svelte index 03bcd5ccc4..3f30558227 100644 --- a/packages/standard-components/src/DataList.svelte +++ b/packages/standard-components/src/DataList.svelte @@ -2,7 +2,6 @@ import { onMount } from "svelte" export let _bb - export let _instanceId export let model export let layout = "list" @@ -12,7 +11,7 @@ async function fetchData() { if (!model || !model.length) return - const FETCH_RECORDS_URL = `/api/${_instanceId}/views/all_${model}` + const FETCH_RECORDS_URL = `/api/views/all_${model}` const response = await _bb.api.get(FETCH_RECORDS_URL) if (response.status === 200) { const json = await response.json() diff --git a/packages/standard-components/src/DataTable.svelte b/packages/standard-components/src/DataTable.svelte index 90716383da..188671b06a 100644 --- a/packages/standard-components/src/DataTable.svelte +++ b/packages/standard-components/src/DataTable.svelte @@ -3,14 +3,13 @@ export let _bb export let onLoad - export let _instanceId export let model let headers = [] let store = _bb.store async function fetchData() { - const FETCH_RECORDS_URL = `/api/${_instanceId}/views/all_${model}` + const FETCH_RECORDS_URL = `/api/views/all_${model}` const response = await _bb.api.get(FETCH_RECORDS_URL) if (response.status === 200) { diff --git a/packages/standard-components/src/List.svelte b/packages/standard-components/src/List.svelte index 7720094999..9e4ddd6c99 100644 --- a/packages/standard-components/src/List.svelte +++ b/packages/standard-components/src/List.svelte @@ -2,7 +2,6 @@ import { onMount } from "svelte" export let _bb - export let _instanceId export let model export let layout = "list" @@ -13,7 +12,7 @@ async function fetchData() { if (!model || !model.length) return - const FETCH_RECORDS_URL = `/api/${_instanceId}/views/all_${model}` + const FETCH_RECORDS_URL = `/api/views/all_${model}` const response = await _bb.api.get(FETCH_RECORDS_URL) if (response.status === 200) { const json = await response.json() diff --git a/packages/standard-components/src/Login.svelte b/packages/standard-components/src/Login.svelte index b52cc154ea..0675c65f46 100644 --- a/packages/standard-components/src/Login.svelte +++ b/packages/standard-components/src/Login.svelte @@ -13,30 +13,17 @@ let password = "" let loading = false let error = false - let _logo = "" let _buttonClass = "" let _inputClass = "" $: { - _logo = _bb.relativeUrl(logo) _buttonClass = buttonClass || "default-button" _inputClass = inputClass || "default-input" } const login = async () => { loading = true - const response = await fetch(_bb.relativeUrl("/api/authenticate"), { - body: JSON.stringify({ - username, - password, - }), - headers: { - "Content-Type": "application/json", - "x-user-agent": "Budibase Builder", - }, - method: "POST", - }) - + const response = await _bb.api.post("/api/authenticate", { username, password }) if (response.status === 200) { const json = await response.json() localStorage.setItem("budibase:token", json.token) @@ -51,9 +38,9 @@
- {#if _logo} + {#if logo}
- logo + logo
{/if} diff --git a/packages/standard-components/src/Test/createApp.js b/packages/standard-components/src/Test/createApp.js index 65ad4792c4..48f65c5789 100644 --- a/packages/standard-components/src/Test/createApp.js +++ b/packages/standard-components/src/Test/createApp.js @@ -15,7 +15,7 @@ export default async () => { const { initialisePage } = createApp( window.document, componentLibraries, - { appRootPath: "" }, + {}, appDef, user, {},