diff --git a/charts/budibase/templates/secrets.yaml b/charts/budibase/templates/secrets.yaml index 1c0a914ed3..263934187e 100644 --- a/charts/budibase/templates/secrets.yaml +++ b/charts/budibase/templates/secrets.yaml @@ -1,4 +1,5 @@ -{{- if .Values.globals.createSecrets -}} +{{- $existingSecret := lookup "v1" "Secret" .Release.Namespace (include "budibase.fullname" .) }} +{{- if .Values.globals.createSecrets }} apiVersion: v1 kind: Secret metadata: @@ -10,8 +11,15 @@ metadata: heritage: "{{ .Release.Service }}" type: Opaque data: + {{- if $existingSecret }} + internalApiKey: {{ index $existingSecret.data "internalApiKey" }} + jwtSecret: {{ index $existingSecret.data "jwtSecret" }} + objectStoreAccess: {{ index $existingSecret.data "objectStoreAccess" }} + objectStoreSecret: {{ index $existingSecret.data "objectStoreSecret" }} + {{- else }} internalApiKey: {{ template "budibase.defaultsecret" .Values.globals.internalApiKey }} jwtSecret: {{ template "budibase.defaultsecret" .Values.globals.jwtSecret }} objectStoreAccess: {{ template "budibase.defaultsecret" .Values.services.objectStore.accessKey }} objectStoreSecret: {{ template "budibase.defaultsecret" .Values.services.objectStore.secretKey }} -{{- end -}} + {{- end }} +{{- end }} diff --git a/lerna.json b/lerna.json index 22aea7d0ae..14b8109e64 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "2.9.33-alpha.8", + "version": "2.9.39-alpha.1", "npmClient": "yarn", "packages": [ "packages/*" diff --git a/packages/backend-core/package.json b/packages/backend-core/package.json index 1d6aa5b9fd..970a4aab8d 100644 --- a/packages/backend-core/package.json +++ b/packages/backend-core/package.json @@ -6,7 +6,7 @@ "types": "dist/src/index.d.ts", "exports": { ".": "./dist/index.js", - "./tests": "./dist/tests.js", + "./tests": "./dist/tests/index.js", "./*": "./dist/*.js" }, "author": "Budibase", diff --git a/packages/backend-core/scripts/build.js b/packages/backend-core/scripts/build.js index bd00cbc7ff..8a15d09708 100644 --- a/packages/backend-core/scripts/build.js +++ b/packages/backend-core/scripts/build.js @@ -1,6 +1,21 @@ #!/usr/bin/node + const coreBuild = require("../../../scripts/build") coreBuild("./src/plugin/index.ts", "./dist/plugins.js") coreBuild("./src/index.ts", "./dist/index.js") -coreBuild("./tests/index.ts", "./dist/tests.js") + +const glob = require("glob") +const inputFiles = [ + ...glob.sync("./src/**/*.[tj]s", { nodir: true }), + ...glob.sync("./tests/**/*.[tj]s", { nodir: true }), +] + +const path = require("path") +for (const file of inputFiles) { + coreBuild(file, `./${path.join("dist", file.replace(/\.ts$/, ".js"))}`, { + skipMeta: true, + bundle: false, + forcedFormat: "cjs", + }) +} diff --git a/packages/backend-core/src/db/couch/DatabaseImpl.ts b/packages/backend-core/src/db/couch/DatabaseImpl.ts index 89f76769b3..29ca4123f5 100644 --- a/packages/backend-core/src/db/couch/DatabaseImpl.ts +++ b/packages/backend-core/src/db/couch/DatabaseImpl.ts @@ -8,7 +8,6 @@ import { DatabasePutOpts, DatabaseCreateIndexOpts, DatabaseDeleteIndexOpts, - DocExistsResponse, Document, isDocument, } from "@budibase/types" @@ -121,19 +120,6 @@ export class DatabaseImpl implements Database { return this.updateOutput(() => db.get(id)) } - async docExists(docId: string): Promise { - const db = await this.checkSetup() - let _rev, exists - try { - const { etag } = await db.head(docId) - _rev = etag - exists = true - } catch (err) { - exists = false - } - return { _rev, exists } - } - async remove(idOrDoc: string | Document, rev?: string) { const db = await this.checkSetup() let _id: string diff --git a/packages/backend-core/src/db/db.ts b/packages/backend-core/src/db/db.ts index f13eb9a965..9aae64b892 100644 --- a/packages/backend-core/src/db/db.ts +++ b/packages/backend-core/src/db/db.ts @@ -11,7 +11,11 @@ export function getDB(dbName?: string, opts?: any): Database { // we have to use a callback for this so that we can close // the DB when we're done, without this manual requests would // need to close the database when done with it to avoid memory leaks -export async function doWithDB(dbName: string, cb: any, opts = {}) { +export async function doWithDB( + dbName: string, + cb: (db: Database) => Promise, + opts = {} +) { const db = getDB(dbName, opts) // need this to be async so that we can correctly close DB after all // async operations have been completed diff --git a/packages/backend-core/src/security/permissions.ts b/packages/backend-core/src/security/permissions.ts index aa0b20a30c..13083534b1 100644 --- a/packages/backend-core/src/security/permissions.ts +++ b/packages/backend-core/src/security/permissions.ts @@ -87,6 +87,7 @@ export const BUILTIN_PERMISSIONS = { new Permission(PermissionType.QUERY, PermissionLevel.WRITE), new Permission(PermissionType.TABLE, PermissionLevel.WRITE), new Permission(PermissionType.AUTOMATION, PermissionLevel.EXECUTE), + new Permission(PermissionType.LEGACY_VIEW, PermissionLevel.READ), ], }, POWER: { @@ -97,6 +98,7 @@ export const BUILTIN_PERMISSIONS = { new Permission(PermissionType.USER, PermissionLevel.READ), new Permission(PermissionType.AUTOMATION, PermissionLevel.EXECUTE), new Permission(PermissionType.WEBHOOK, PermissionLevel.READ), + new Permission(PermissionType.LEGACY_VIEW, PermissionLevel.READ), ], }, ADMIN: { @@ -108,6 +110,7 @@ export const BUILTIN_PERMISSIONS = { new Permission(PermissionType.AUTOMATION, PermissionLevel.ADMIN), new Permission(PermissionType.WEBHOOK, PermissionLevel.READ), new Permission(PermissionType.QUERY, PermissionLevel.ADMIN), + new Permission(PermissionType.LEGACY_VIEW, PermissionLevel.READ), ], }, } diff --git a/packages/backend-core/src/security/roles.ts b/packages/backend-core/src/security/roles.ts index 081193b433..05d536562b 100644 --- a/packages/backend-core/src/security/roles.ts +++ b/packages/backend-core/src/security/roles.ts @@ -253,7 +253,7 @@ export function checkForRoleResourceArray( * Given an app ID this will retrieve all of the roles that are currently within that app. * @return {Promise} An array of the role objects that were found. */ -export async function getAllRoles(appId?: string) { +export async function getAllRoles(appId?: string): Promise { if (appId) { return doWithDB(appId, internal) } else { @@ -312,37 +312,6 @@ export async function getAllRoles(appId?: string) { } } -/** - * This retrieves the required role for a resource - * @param permLevel The level of request - * @param resourceId The resource being requested - * @param subResourceId The sub resource being requested - * @return {Promise<{permissions}|Object>} returns the permissions required to access. - */ -export async function getRequiredResourceRole( - permLevel: string, - { resourceId, subResourceId }: { resourceId?: string; subResourceId?: string } -) { - const roles = await getAllRoles() - let main = [], - sub = [] - for (let role of roles) { - // no permissions, ignore it - if (!role.permissions) { - continue - } - const mainRes = resourceId ? role.permissions[resourceId] : undefined - const subRes = subResourceId ? role.permissions[subResourceId] : undefined - if (mainRes && mainRes.indexOf(permLevel) !== -1) { - main.push(role._id) - } else if (subRes && subRes.indexOf(permLevel) !== -1) { - sub.push(role._id) - } - } - // for now just return the IDs - return main.concat(sub) -} - export class AccessController { userHierarchies: { [key: string]: string[] } constructor() { diff --git a/packages/bbui/src/Tooltip/TempTooltip.svelte b/packages/bbui/src/Tooltip/TempTooltip.svelte index 0d590b1ec6..6da01a2bfc 100644 --- a/packages/bbui/src/Tooltip/TempTooltip.svelte +++ b/packages/bbui/src/Tooltip/TempTooltip.svelte @@ -4,7 +4,7 @@ export let text = null export let condition = true - export let duration = 3000 + export let duration = 5000 export let position export let type diff --git a/packages/builder/src/builderStore/store/screenTemplates/rowListScreen.js b/packages/builder/src/builderStore/store/screenTemplates/rowListScreen.js index 968d6deb4a..54dd38ac63 100644 --- a/packages/builder/src/builderStore/store/screenTemplates/rowListScreen.js +++ b/packages/builder/src/builderStore/store/screenTemplates/rowListScreen.js @@ -3,6 +3,9 @@ import { Screen } from "./utils/Screen" import { Component } from "./utils/Component" export default function (datasources) { + if (!Array.isArray(datasources)) { + return [] + } return datasources.map(datasource => { return { name: `${datasource.name} - List`, diff --git a/packages/builder/src/components/automation/AutomationBuilder/FlowChart/FlowItem.svelte b/packages/builder/src/components/automation/AutomationBuilder/FlowChart/FlowItem.svelte index 7a02433411..85c3776fdb 100644 --- a/packages/builder/src/components/automation/AutomationBuilder/FlowChart/FlowItem.svelte +++ b/packages/builder/src/components/automation/AutomationBuilder/FlowChart/FlowItem.svelte @@ -73,7 +73,7 @@ if (!perms["execute"]) { role = "BASIC" } else { - role = perms["execute"] + role = perms["execute"].role } } diff --git a/packages/builder/src/components/backend/DataTable/ViewDataTable.svelte b/packages/builder/src/components/backend/DataTable/ViewDataTable.svelte index d239cabd59..f6160e3caa 100644 --- a/packages/builder/src/components/backend/DataTable/ViewDataTable.svelte +++ b/packages/builder/src/components/backend/DataTable/ViewDataTable.svelte @@ -10,6 +10,7 @@ import ManageAccessButton from "./buttons/ManageAccessButton.svelte" import HideAutocolumnButton from "./buttons/HideAutocolumnButton.svelte" import { notifications } from "@budibase/bbui" + import { ROW_EXPORT_FORMATS } from "constants/backend" export let view = {} @@ -19,6 +20,14 @@ let type = "internal" $: name = view.name + $: calculation = view.calculation + + $: supportedFormats = Object.values(ROW_EXPORT_FORMATS).filter(key => { + if (calculation && key === ROW_EXPORT_FORMATS.JSON_WITH_SCHEMA) { + return false + } + return true + }) // Fetch rows for specified view $: fetchViewData(name, view.field, view.groupBy, view.calculation) @@ -68,5 +77,5 @@ {/if} - + diff --git a/packages/builder/src/components/backend/DataTable/buttons/ExportButton.svelte b/packages/builder/src/components/backend/DataTable/buttons/ExportButton.svelte index fd0d64f6cd..4fa1d07abd 100644 --- a/packages/builder/src/components/backend/DataTable/buttons/ExportButton.svelte +++ b/packages/builder/src/components/backend/DataTable/buttons/ExportButton.svelte @@ -7,6 +7,7 @@ export let sorting export let disabled = false export let selectedRows + export let formats let modal @@ -15,5 +16,5 @@ Export - + diff --git a/packages/builder/src/components/backend/DataTable/buttons/ManageAccessButton.svelte b/packages/builder/src/components/backend/DataTable/buttons/ManageAccessButton.svelte index f6a74784fa..5c0b7df742 100644 --- a/packages/builder/src/components/backend/DataTable/buttons/ManageAccessButton.svelte +++ b/packages/builder/src/components/backend/DataTable/buttons/ManageAccessButton.svelte @@ -5,25 +5,19 @@ export let resourceId export let disabled = false - export let requiresLicence let modal let resourcePermissions - async function openDropdown() { - resourcePermissions = await permissions.forResource(resourceId) + async function openModal() { + resourcePermissions = await permissions.forResourceDetailed(resourceId) modal.show() } - + Access - + diff --git a/packages/builder/src/components/backend/DataTable/buttons/grid/GridCreateViewButton.svelte b/packages/builder/src/components/backend/DataTable/buttons/grid/GridCreateViewButton.svelte index 3244ce3277..3441d8de17 100644 --- a/packages/builder/src/components/backend/DataTable/buttons/grid/GridCreateViewButton.svelte +++ b/packages/builder/src/components/backend/DataTable/buttons/grid/GridCreateViewButton.svelte @@ -1,18 +1,30 @@ - - Add view - + + + Create view + + diff --git a/packages/builder/src/components/backend/DataTable/buttons/grid/GridManageAccessButton.svelte b/packages/builder/src/components/backend/DataTable/buttons/grid/GridManageAccessButton.svelte index e4c48528f4..0cd008bab1 100644 --- a/packages/builder/src/components/backend/DataTable/buttons/grid/GridManageAccessButton.svelte +++ b/packages/builder/src/components/backend/DataTable/buttons/grid/GridManageAccessButton.svelte @@ -1,5 +1,4 @@ - + diff --git a/packages/builder/src/components/backend/DataTable/modals/ExportModal.svelte b/packages/builder/src/components/backend/DataTable/modals/ExportModal.svelte index ac168698fc..09f76d3522 100644 --- a/packages/builder/src/components/backend/DataTable/modals/ExportModal.svelte +++ b/packages/builder/src/components/backend/DataTable/modals/ExportModal.svelte @@ -9,30 +9,43 @@ import download from "downloadjs" import { API } from "api" import { Constants, LuceneUtils } from "@budibase/frontend-core" - - const FORMATS = [ - { - name: "CSV", - key: "csv", - }, - { - name: "JSON", - key: "json", - }, - { - name: "JSON with Schema", - key: "jsonWithSchema", - }, - ] + import { ROW_EXPORT_FORMATS } from "constants/backend" export let view export let filters export let sorting export let selectedRows = [] + export let formats - let exportFormat = FORMATS[0].key + const FORMATS = [ + { + name: "CSV", + key: ROW_EXPORT_FORMATS.CSV, + }, + { + name: "JSON", + key: ROW_EXPORT_FORMATS.JSON, + }, + { + name: "JSON with Schema", + key: ROW_EXPORT_FORMATS.JSON_WITH_SCHEMA, + }, + ] + + $: options = FORMATS.filter(format => { + if (formats && !formats.includes(format.key)) { + return false + } + return true + }) + + let exportFormat let filterLookup + $: if (options && !exportFormat) { + exportFormat = Array.isArray(options) ? options[0]?.key : [] + } + $: luceneFilter = LuceneUtils.buildLuceneQuery(filters) $: exportOpDisplay = buildExportOpDisplay(sorting, filterDisplay, filters) @@ -190,7 +203,7 @@ - + { - return items.map(item => { - return { - id: listItemKey ? item[listItemKey] : generate(), - item, - } - }) + const buildDraggable = items => { + return items + .map(item => { + return { + id: listItemKey ? item[listItemKey] : generate(), + item, + } + }) + .filter(item => item.id) } $: if (items) { - draggableItems = buildDragable(items) + draggableItems = buildDraggable(items) } const updateRowOrder = e => { diff --git a/packages/builder/src/components/design/settings/controls/FieldConfiguration/FieldConfiguration.svelte b/packages/builder/src/components/design/settings/controls/FieldConfiguration/FieldConfiguration.svelte index 255f46ec7b..f12e8d27ae 100644 --- a/packages/builder/src/components/design/settings/controls/FieldConfiguration/FieldConfiguration.svelte +++ b/packages/builder/src/components/design/settings/controls/FieldConfiguration/FieldConfiguration.svelte @@ -99,6 +99,9 @@ } const type = getComponentForField(instance.field, schema) + if (!type) { + return null + } instance._component = `@budibase/standard-components/${type}` const pseudoComponentInstance = store.actions.components.createInstance( @@ -116,7 +119,9 @@ } $: if (sanitisedFields) { - fieldList = [...sanitisedFields, ...unconfigured].map(buildSudoInstance) + fieldList = [...sanitisedFields, ...unconfigured] + .map(buildSudoInstance) + .filter(x => x != null) } const processItemUpdate = e => { diff --git a/packages/builder/src/components/integration/AccessLevelSelect.svelte b/packages/builder/src/components/integration/AccessLevelSelect.svelte index 091f33cbcd..3dc24983d3 100644 --- a/packages/builder/src/components/integration/AccessLevelSelect.svelte +++ b/packages/builder/src/components/integration/AccessLevelSelect.svelte @@ -40,7 +40,7 @@ return } try { - roleId = (await permissions.forResource(queryToFetch._id))["read"] + roleId = (await permissions.forResource(queryToFetch._id))["read"].role } catch (err) { roleId = Constants.Roles.BASIC } diff --git a/packages/builder/src/constants/backend/index.js b/packages/builder/src/constants/backend/index.js index 2c07f8f431..ed0549eeca 100644 --- a/packages/builder/src/constants/backend/index.js +++ b/packages/builder/src/constants/backend/index.js @@ -287,3 +287,9 @@ export const DatasourceTypes = { GRAPH: "Graph", API: "API", } + +export const ROW_EXPORT_FORMATS = { + CSV: "csv", + JSON: "json", + JSON_WITH_SCHEMA: "jsonWithSchema", +} diff --git a/packages/builder/src/pages/builder/app/[application]/_components/BuilderSidePanel.svelte b/packages/builder/src/pages/builder/app/[application]/_components/BuilderSidePanel.svelte index 3e6741e920..7c4d3db7ce 100644 --- a/packages/builder/src/pages/builder/app/[application]/_components/BuilderSidePanel.svelte +++ b/packages/builder/src/pages/builder/app/[application]/_components/BuilderSidePanel.svelte @@ -120,7 +120,7 @@ await usersFetch.refresh() filteredUsers = $usersFetch.rows - .filter(user => !user?.admin?.global) // filter out global admins + .filter(user => user.email !== $auth.user.email) .map(user => { const isAdminOrGlobalBuilder = sdk.users.isAdminOrGlobalBuilder( user, @@ -150,13 +150,10 @@ } const sortInviteRoles = (a, b) => { - const aEmpty = - !a.info?.appBuilders?.length && Object.keys(a.info.apps).length === 0 - const bEmpty = - !b.info?.appBuilders?.length && Object.keys(b.info.apps).length === 0 + const aAppsEmpty = !a.info?.apps?.length && !a.info?.builder?.apps?.length + const bAppsEmpty = !b.info?.apps?.length && !b.info?.builder?.apps?.length - if (aEmpty && !bEmpty) return 1 - if (!aEmpty && bEmpty) return -1 + return aAppsEmpty && !bAppsEmpty ? 1 : !aAppsEmpty && bAppsEmpty ? -1 : 0 } const sortRoles = (a, b) => { @@ -366,18 +363,19 @@ const payload = [ { email: newUserEmail, - builder: !!creationRoleType === Constants.BudibaseRoles.Admin, - admin: !!creationRoleType === Constants.BudibaseRoles.Admin, + builder: { global: creationRoleType === Constants.BudibaseRoles.Admin }, + admin: { global: creationRoleType === Constants.BudibaseRoles.Admin }, }, ] - if (creationAccessType === Constants.Roles.CREATOR) { - payload[0].appBuilders = [prodAppId] - } else { - payload[0].apps = { - [prodAppId]: creationAccessType, - } + const notCreatingAdmin = creationRoleType !== Constants.BudibaseRoles.Admin + const isCreator = creationAccessType === Constants.Roles.CREATOR + if (notCreatingAdmin && isCreator) { + payload[0].builder.apps = [prodAppId] + } else if (notCreatingAdmin && !isCreator) { + payload[0].apps = { [prodAppId]: creationAccessType } } + let userInviteResponse try { userInviteResponse = await users.onboard(payload) @@ -438,10 +436,11 @@ } if (role === Constants.Roles.CREATOR) { - updateBody.appBuilders = [...(updateBody.appBuilders ?? []), prodAppId] + updateBody.builder = updateBody.builder || {} + updateBody.builder.apps = [...(updateBody.builder.apps ?? []), prodAppId] delete updateBody?.apps?.[prodAppId] - } else if (role !== Constants.Roles.CREATOR && invite?.appBuilders) { - invite.appBuilders = [] + } else if (role !== Constants.Roles.CREATOR && invite?.builder?.apps) { + invite.builder.apps = [] } await users.updateInvite(updateBody) await filterInvites(query) @@ -494,6 +493,18 @@ } } + const getInviteRoleValue = invite => { + if (invite.info?.admin?.global && invite.info?.builder?.global) { + return Constants.Roles.ADMIN + } + + if (invite.info?.builder?.apps?.includes(prodAppId)) { + return Constants.Roles.CREATOR + } + + return invite.info.apps?.[prodAppId] + } + const getRoleFooter = user => { if (user.group) { const role = $roles.find(role => role._id === user.role) @@ -531,7 +542,9 @@ {invitingFlow ? "Invite new user" : "Users"}
- + {#if !invitingFlow} + + {/if} Access
{#each filteredInvites as invite} + {@const user = { + isAdminOrGlobalBuilder: + invite.info?.admin?.global && invite.info?.builder?.global, + }} +
@@ -608,10 +626,9 @@
diff --git a/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/CreateScreenModal.svelte b/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/CreateScreenModal.svelte index dd611041ed..9a96242b30 100644 --- a/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/CreateScreenModal.svelte +++ b/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/CreateScreenModal.svelte @@ -110,7 +110,7 @@ if (mode === "table") { datasourceModal.show() } else if (mode === "blank") { - let templates = getTemplates($store, $tables.list) + let templates = getTemplates($tables.list) const blankScreenTemplate = templates.find( t => t.id === "createFromScratch" ) diff --git a/packages/builder/src/pages/builder/portal/users/groups/[groupId].svelte b/packages/builder/src/pages/builder/portal/users/groups/[groupId].svelte index 793ba5c09f..4c1bb661fd 100644 --- a/packages/builder/src/pages/builder/portal/users/groups/[groupId].svelte +++ b/packages/builder/src/pages/builder/portal/users/groups/[groupId].svelte @@ -46,7 +46,7 @@ let loaded = false let editModal, deleteModal - $: console.log(group) + $: scimEnabled = $features.isScimEnabled $: readonly = !sdk.users.isAdmin($auth.user) || scimEnabled $: group = $groups.find(x => x._id === groupId) @@ -62,7 +62,7 @@ ? Constants.Roles.CREATOR : group?.roles?.[apps.getProdAppID(app.devId)], })) - $: console.log(groupApps) + $: { if (loaded && !group?._id) { $goto("./") diff --git a/packages/builder/src/pages/builder/portal/users/users/_components/AppsTableRenderer.svelte b/packages/builder/src/pages/builder/portal/users/users/_components/AppsTableRenderer.svelte index b7bb810aba..32e50ef05f 100644 --- a/packages/builder/src/pages/builder/portal/users/users/_components/AppsTableRenderer.svelte +++ b/packages/builder/src/pages/builder/portal/users/users/_components/AppsTableRenderer.svelte @@ -5,7 +5,6 @@ export let value export let row - $: console.log(row) $: priviliged = sdk.users.isAdminOrBuilder(row) $: count = getCount(row) @@ -14,10 +13,10 @@ return $apps.length } else { return sdk.users.hasAppBuilderPermissions(row) - ? row.builder.apps.length + - Object.keys(row.roles || {}).filter(appId => - row.builder.apps.includes(appId) - ).length + ? row?.builder?.apps?.length + + Object.keys(row.roles || {}).filter(appId => { + row?.builder?.apps?.includes(appId) + }).length : value?.length || 0 } } diff --git a/packages/builder/src/pages/builder/portal/users/users/_components/RoleTableRenderer.svelte b/packages/builder/src/pages/builder/portal/users/users/_components/RoleTableRenderer.svelte index 8217c85c5e..43fe8cc1cc 100644 --- a/packages/builder/src/pages/builder/portal/users/users/_components/RoleTableRenderer.svelte +++ b/packages/builder/src/pages/builder/portal/users/users/_components/RoleTableRenderer.svelte @@ -10,7 +10,7 @@ admin: "Full access", } - $: role = Constants.BudibaseRoleOptions.find( + $: role = Constants.BudibaseRoleOptionsOld.find( x => x.value === users.getUserRole(row) ) $: value = role?.label || "Not available" diff --git a/packages/builder/src/stores/backend/permissions.js b/packages/builder/src/stores/backend/permissions.js index aaab406bc9..0113f221c0 100644 --- a/packages/builder/src/stores/backend/permissions.js +++ b/packages/builder/src/stores/backend/permissions.js @@ -13,9 +13,22 @@ export function createPermissionStore() { level, }) }, + remove: async ({ level, role, resource }) => { + return await API.removePermissionFromResource({ + resourceId: resource, + roleId: role, + level, + }) + }, forResource: async resourceId => { + return (await API.getPermissionForResource(resourceId)).permissions + }, + forResourceDetailed: async resourceId => { return await API.getPermissionForResource(resourceId) }, + getDependantsInfo: async resourceId => { + return await API.getDependants(resourceId) + }, } } diff --git a/packages/builder/src/stores/portal/users.js b/packages/builder/src/stores/portal/users.js index 996a94013b..3ee0516544 100644 --- a/packages/builder/src/stores/portal/users.js +++ b/packages/builder/src/stores/portal/users.js @@ -121,8 +121,11 @@ export function createUsersStore() { } const getUserRole = user => - sdk.users.isAdminOrGlobalBuilder(user) ? "admin" : "appUser" - + sdk.users.isAdmin(user) + ? "admin" + : sdk.users.isBuilder(user) + ? "developer" + : "appUser" const refreshUsage = fn => async (...args) => { diff --git a/packages/client/src/components/app/forms/InnerForm.svelte b/packages/client/src/components/app/forms/InnerForm.svelte index b4370993a8..d9c335e8ce 100644 --- a/packages/client/src/components/app/forms/InnerForm.svelte +++ b/packages/client/src/components/app/forms/InnerForm.svelte @@ -136,7 +136,7 @@ // Check arrays - remove any values not present in the field schema and // convert any values supplied to strings if (Array.isArray(value) && type === "array" && schema) { - const options = schema?.constraints.inclusion || [] + const options = schema?.constraints?.inclusion || [] return value.map(opt => String(opt)).filter(opt => options.includes(opt)) } return value diff --git a/packages/frontend-core/src/api/permissions.js b/packages/frontend-core/src/api/permissions.js index 5407cb3ce5..dd0005aaf2 100644 --- a/packages/frontend-core/src/api/permissions.js +++ b/packages/frontend-core/src/api/permissions.js @@ -21,4 +21,27 @@ export const buildPermissionsEndpoints = API => ({ url: `/api/permission/${roleId}/${resourceId}/${level}`, }) }, + + /** + * Remove the the permissions for a certain resource + * @param resourceId the ID of the resource to update + * @param roleId the ID of the role to update the permissions of + * @param level the level to remove the role for this resource + * @return {Promise<*>} + */ + removePermissionFromResource: async ({ resourceId, roleId, level }) => { + return await API.delete({ + url: `/api/permission/${roleId}/${resourceId}/${level}`, + }) + }, + + /** + * Gets info about the resources that depend on this resource permissions + * @param resourceId the resource ID to check + */ + getDependants: async resourceId => { + return await API.get({ + url: `/api/permission/${resourceId}/dependants`, + }) + }, }) diff --git a/packages/frontend-core/src/api/user.js b/packages/frontend-core/src/api/user.js index 329e9cc499..6c616d7baf 100644 --- a/packages/frontend-core/src/api/user.js +++ b/packages/frontend-core/src/api/user.js @@ -144,8 +144,8 @@ export const buildUserEndpoints = API => ({ body: { email, userInfo: { - admin: admin ? { global: true } : undefined, - builder: builder ? { global: true } : undefined, + admin: admin?.global ? { global: true } : undefined, + builder: builder?.global ? { global: true } : undefined, apps: apps ? apps : undefined, }, }, @@ -156,14 +156,13 @@ export const buildUserEndpoints = API => ({ return await API.post({ url: "/api/global/users/onboard", body: payload.map(invite => { - const { email, admin, builder, apps, appBuilders } = invite + const { email, admin, builder, apps } = invite return { email, userInfo: { - admin: admin ? { global: true } : undefined, - builder: builder ? { global: true } : undefined, + admin, + builder, apps: apps ? apps : undefined, - appBuilders, }, } }), @@ -176,12 +175,11 @@ export const buildUserEndpoints = API => ({ * @param invite the invite code sent in the email */ updateUserInvite: async invite => { - console.log(invite) await API.post({ url: `/api/global/users/invite/update/${invite.code}`, body: { apps: invite.apps, - appBuilders: invite.appBuilders, + builder: invite.builder, }, }) }, diff --git a/packages/frontend-core/src/constants.js b/packages/frontend-core/src/constants.js index 198c8985d4..cfaaaea81b 100644 --- a/packages/frontend-core/src/constants.js +++ b/packages/frontend-core/src/constants.js @@ -2,6 +2,7 @@ * Operator options for lucene queries */ export { OperatorOptions, SqlNumberTypeRangeMap } from "@budibase/shared-core" +export { Feature as Features } from "@budibase/types" // Cookie names export const Cookies = { @@ -22,6 +23,11 @@ export const BudibaseRoles = { Admin: "admin", } +export const BudibaseRoleOptionsOld = [ + { label: "Developer", value: BudibaseRoles.Developer }, + { label: "Member", value: BudibaseRoles.AppUser }, + { label: "Admin", value: BudibaseRoles.Admin }, +] export const BudibaseRoleOptions = [ { label: "Member", value: BudibaseRoles.AppUser }, { label: "Admin", value: BudibaseRoles.Admin }, diff --git a/packages/server/src/api/controllers/application.ts b/packages/server/src/api/controllers/application.ts index bd5960e858..012aa7c66d 100644 --- a/packages/server/src/api/controllers/application.ts +++ b/packages/server/src/api/controllers/application.ts @@ -39,8 +39,9 @@ import { } from "../../db/defaultData/datasource_bb_default" import { removeAppFromUserRoles } from "../../utilities/workerRequests" import { stringToReadStream } from "../../utilities" -import { doesUserHaveLock } from "../../utilities/redis" +import { doesUserHaveLock, getLocksById } from "../../utilities/redis" import { cleanupAutomations } from "../../automations/utils" +import { checkAppMetadata } from "../../automations/logging" import { getUniqueRows } from "../../utilities/usageQuota/rows" import { groups, licensing, quotas } from "@budibase/pro" import { @@ -50,6 +51,7 @@ import { PlanType, Screen, UserCtx, + ContextUser, } from "@budibase/types" import { BASE_LAYOUT_PROP_IDS } from "../../constants/layouts" import sdk from "../../sdk" diff --git a/packages/server/src/api/controllers/automation.ts b/packages/server/src/api/controllers/automation.ts index c861782a02..a4697f99f2 100644 --- a/packages/server/src/api/controllers/automation.ts +++ b/packages/server/src/api/controllers/automation.ts @@ -20,7 +20,7 @@ import { Automation, AutomationActionStepId, AutomationResults, - Ctx, + BBContext, } from "@budibase/types" import { getActionDefinitions as actionDefs } from "../../automations/actions" import sdk from "../../sdk" @@ -73,7 +73,7 @@ function cleanAutomationInputs(automation: Automation) { return automation } -export async function create(ctx: Ctx) { +export async function create(ctx: BBContext) { const db = context.getAppDB() let automation = ctx.request.body automation.appId = ctx.appId @@ -142,7 +142,7 @@ export async function handleStepEvents( } } -export async function update(ctx: Ctx) { +export async function update(ctx: BBContext) { const db = context.getAppDB() let automation = ctx.request.body automation.appId = ctx.appId @@ -193,7 +193,7 @@ export async function update(ctx: Ctx) { builderSocket?.emitAutomationUpdate(ctx, automation) } -export async function fetch(ctx: Ctx) { +export async function fetch(ctx: BBContext) { const db = context.getAppDB() const response = await db.allDocs( getAutomationParams(null, { @@ -203,11 +203,12 @@ export async function fetch(ctx: Ctx) { ctx.body = response.rows.map(row => row.doc) } -export async function find(ctx: Ctx) { - ctx.body = await sdk.automations.get(ctx.params.id) +export async function find(ctx: BBContext) { + const db = context.getAppDB() + ctx.body = await db.get(ctx.params.id) } -export async function destroy(ctx: Ctx) { +export async function destroy(ctx: BBContext) { const db = context.getAppDB() const automationId = ctx.params.id const oldAutomation = await db.get(automationId) @@ -221,11 +222,11 @@ export async function destroy(ctx: Ctx) { builderSocket?.emitAutomationDeletion(ctx, automationId) } -export async function logSearch(ctx: Ctx) { +export async function logSearch(ctx: BBContext) { ctx.body = await automations.logs.logSearch(ctx.request.body) } -export async function clearLogError(ctx: Ctx) { +export async function clearLogError(ctx: BBContext) { const { automationId, appId } = ctx.request.body await context.doInAppContext(appId, async () => { const db = context.getProdAppDB() @@ -244,15 +245,15 @@ export async function clearLogError(ctx: Ctx) { }) } -export async function getActionList(ctx: Ctx) { +export async function getActionList(ctx: BBContext) { ctx.body = await getActionDefinitions() } -export async function getTriggerList(ctx: Ctx) { +export async function getTriggerList(ctx: BBContext) { ctx.body = getTriggerDefinitions() } -export async function getDefinitionList(ctx: Ctx) { +export async function getDefinitionList(ctx: BBContext) { ctx.body = { trigger: getTriggerDefinitions(), action: await getActionDefinitions(), @@ -265,7 +266,7 @@ export async function getDefinitionList(ctx: Ctx) { * * *********************/ -export async function trigger(ctx: Ctx) { +export async function trigger(ctx: BBContext) { const db = context.getAppDB() let automation = await db.get(ctx.params.id) @@ -310,7 +311,7 @@ function prepareTestInput(input: any) { return input } -export async function test(ctx: Ctx) { +export async function test(ctx: BBContext) { const db = context.getAppDB() let automation = await db.get(ctx.params.id) await setTestFlag(automation._id!) diff --git a/packages/server/src/api/controllers/permission.ts b/packages/server/src/api/controllers/permission.ts index 8314f29398..a9cd686674 100644 --- a/packages/server/src/api/controllers/permission.ts +++ b/packages/server/src/api/controllers/permission.ts @@ -1,5 +1,13 @@ import { permissions, roles, context, HTTPError } from "@budibase/backend-core" -import { UserCtx, Database, Role, PermissionLevel } from "@budibase/types" +import { + UserCtx, + Database, + Role, + PermissionLevel, + GetResourcePermsResponse, + ResourcePermissionInfo, + GetDependantResourcesResponse, +} from "@budibase/types" import { getRoleParams } from "../../db/utils" import { CURRENTLY_SUPPORTED_LEVELS, @@ -145,33 +153,40 @@ export async function fetch(ctx: UserCtx) { ctx.body = finalPermissions } -export async function getResourcePerms(ctx: UserCtx) { +export async function getResourcePerms( + ctx: UserCtx +) { const resourceId = ctx.params.resourceId - const db = context.getAppDB() - const body = await db.allDocs( - getRoleParams(null, { - include_docs: true, - }) - ) - const rolesList = body.rows.map(row => row.doc) - let permissions: Record = {} - for (let level of SUPPORTED_LEVELS) { - // update the various roleIds in the resource permissions - for (let role of rolesList) { - const rolePerms = roles.checkForRoleResourceArray( - role.permissions, - resourceId - ) - if ( - rolePerms && - rolePerms[resourceId] && - rolePerms[resourceId].indexOf(level) !== -1 - ) { - permissions[level] = roles.getExternalRoleID(role._id, role.version)! - } - } + const resourcePermissions = await sdk.permissions.getResourcePerms(resourceId) + const inheritablePermissions = + await sdk.permissions.getInheritablePermissions(resourceId) + + ctx.body = { + permissions: Object.entries(resourcePermissions).reduce( + (p, [level, role]) => { + p[level] = { + role: role.role, + permissionType: role.type, + inheritablePermission: + inheritablePermissions && inheritablePermissions[level].role, + } + return p + }, + {} as Record + ), + requiresPlanToModify: ( + await sdk.permissions.allowsExplicitPermissions(resourceId) + ).minPlan, + } +} + +export async function getDependantResources( + ctx: UserCtx +) { + const resourceId = ctx.params.resourceId + ctx.body = { + resourceByType: await sdk.permissions.getDependantResources(resourceId), } - ctx.body = Object.assign(getBasePermissions(resourceId), permissions) } export async function addPermission(ctx: UserCtx) { diff --git a/packages/server/src/api/controllers/row/index.ts b/packages/server/src/api/controllers/row/index.ts index ebe0c32e63..88d3f50dbe 100644 --- a/packages/server/src/api/controllers/row/index.ts +++ b/packages/server/src/api/controllers/row/index.ts @@ -95,7 +95,7 @@ export async function fetchView(ctx: any) { () => sdk.rows.fetchView(tableId, viewName, { calculation, - group, + group: calculation ? group : null, field, }), { diff --git a/packages/server/src/api/controllers/view/exporters.ts b/packages/server/src/api/controllers/view/exporters.ts index ec0aca95a9..e707cad0cc 100644 --- a/packages/server/src/api/controllers/view/exporters.ts +++ b/packages/server/src/api/controllers/view/exporters.ts @@ -27,7 +27,7 @@ export function json(rows: Row[]) { export function jsonWithSchema(schema: TableSchema, rows: Row[]) { const newSchema: TableSchema = {} Object.values(schema).forEach(column => { - if (!column.autocolumn) { + if (!column.autocolumn && column.name) { newSchema[column.name] = column } }) diff --git a/packages/server/src/api/routes/permission.ts b/packages/server/src/api/routes/permission.ts index 7f82d34052..119853e066 100644 --- a/packages/server/src/api/routes/permission.ts +++ b/packages/server/src/api/routes/permission.ts @@ -23,6 +23,11 @@ router authorized(permissions.BUILDER), controller.getResourcePerms ) + .get( + "/api/permission/:resourceId/dependants", + authorized(permissions.BUILDER), + controller.getDependantResources + ) // adding a specific role/level for the resource overrides the underlying access control .post( "/api/permission/:roleId/:resourceId/:level", diff --git a/packages/server/src/api/routes/tests/permissions.spec.ts b/packages/server/src/api/routes/tests/permissions.spec.ts index 3fa21cb677..129bc00b44 100644 --- a/packages/server/src/api/routes/tests/permissions.spec.ts +++ b/packages/server/src/api/routes/tests/permissions.spec.ts @@ -1,5 +1,6 @@ const mockedSdk = sdk.permissions as jest.Mocked jest.mock("../../../sdk/app/permissions", () => ({ + ...jest.requireActual("../../../sdk/app/permissions"), resourceActionAllowed: jest.fn(), })) @@ -78,8 +79,12 @@ describe("/permission", () => { .set(config.defaultHeaders()) .expect("Content-Type", /json/) .expect(200) - expect(res.body["read"]).toEqual(STD_ROLE_ID) - expect(res.body["write"]).toEqual(HIGHER_ROLE_ID) + expect(res.body).toEqual({ + permissions: { + read: { permissionType: "EXPLICIT", role: STD_ROLE_ID }, + write: { permissionType: "BASE", role: HIGHER_ROLE_ID }, + }, + }) }) it("should get resource permissions with multiple roles", async () => { @@ -89,15 +94,20 @@ describe("/permission", () => { level: PermissionLevel.WRITE, }) const res = await config.api.permission.get(table._id) - expect(res.body["read"]).toEqual(STD_ROLE_ID) - expect(res.body["write"]).toEqual(HIGHER_ROLE_ID) + expect(res.body).toEqual({ + permissions: { + read: { permissionType: "EXPLICIT", role: STD_ROLE_ID }, + write: { permissionType: "EXPLICIT", role: HIGHER_ROLE_ID }, + }, + }) + const allRes = await request .get(`/api/permission`) .set(config.defaultHeaders()) .expect("Content-Type", /json/) .expect(200) - expect(allRes.body[table._id]["write"]).toEqual(HIGHER_ROLE_ID) expect(allRes.body[table._id]["read"]).toEqual(STD_ROLE_ID) + expect(allRes.body[table._id]["write"]).toEqual(HIGHER_ROLE_ID) }) it("throw forbidden if the action is not allowed for the resource", async () => { @@ -260,4 +270,21 @@ describe("/permission", () => { expect(publicPerm.name).toBeDefined() }) }) + + describe("default permissions", () => { + it("legacy views", async () => { + const legacyView = await config.createLegacyView() + + const res = await config.api.permission.get(legacyView.name) + + expect(res.body).toEqual({ + permissions: { + read: { + permissionType: "BASE", + role: "BASIC", + }, + }, + }) + }) + }) }) diff --git a/packages/server/src/api/routes/tests/row.spec.ts b/packages/server/src/api/routes/tests/row.spec.ts index f28b96b9d8..f63be2318f 100644 --- a/packages/server/src/api/routes/tests/row.spec.ts +++ b/packages/server/src/api/routes/tests/row.spec.ts @@ -1,11 +1,12 @@ import tk from "timekeeper" import { outputProcessing } from "../../../utilities/rowProcessor" import * as setup from "./utilities" -import { context, tenancy } from "@budibase/backend-core" +import { context, roles, tenancy } from "@budibase/backend-core" import { quotas } from "@budibase/pro" import { FieldType, MonthlyQuotaName, + PermissionLevel, QuotaUsageType, Row, SortOrder, @@ -16,6 +17,7 @@ import { import { expectAnyInternalColsAttributes, generator, + mocks, structures, } from "@budibase/backend-core/tests" @@ -37,6 +39,7 @@ describe("/rows", () => { }) beforeEach(async () => { + mocks.licenses.useCloudFree() table = await config.createTable() row = basicRow(table._id!) }) @@ -670,7 +673,7 @@ describe("/rows", () => { }) it("should be able to run on a view", async () => { - const view = await config.createView() + const view = await config.createLegacyView() const row = await config.createRow() const rowUsage = await getRowUsage() const queryUsage = await getQueryUsage() @@ -1314,6 +1317,85 @@ describe("/rows", () => { bookmark: expect.any(String), }) }) + + describe("permissions", () => { + let viewId: string + let tableId: string + + beforeAll(async () => { + const table = await config.createTable(userTable()) + const rows = [] + for (let i = 0; i < 10; i++) { + rows.push(await config.createRow({ tableId: table._id })) + } + + const createViewResponse = await config.api.viewV2.create() + + tableId = table._id! + viewId = createViewResponse.id + }) + + beforeEach(() => { + mocks.licenses.useViewPermissions() + }) + + it("does not allow public users to fetch by default", async () => { + await config.publish() + await config.api.viewV2.search(viewId, undefined, { + expectStatus: 403, + usePublicUser: true, + }) + }) + + it("allow public users to fetch when permissions are explicit", async () => { + await config.api.permission.set({ + roleId: roles.BUILTIN_ROLE_IDS.PUBLIC, + level: PermissionLevel.READ, + resourceId: viewId, + }) + await config.publish() + + const response = await config.api.viewV2.search(viewId, undefined, { + usePublicUser: true, + }) + + expect(response.body.rows).toHaveLength(10) + }) + + it("allow public users to fetch when permissions are inherited", async () => { + await config.api.permission.set({ + roleId: roles.BUILTIN_ROLE_IDS.PUBLIC, + level: PermissionLevel.READ, + resourceId: tableId, + }) + await config.publish() + + const response = await config.api.viewV2.search(viewId, undefined, { + usePublicUser: true, + }) + + expect(response.body.rows).toHaveLength(10) + }) + + it("respects inherited permissions, not allowing not public views from public tables", async () => { + await config.api.permission.set({ + roleId: roles.BUILTIN_ROLE_IDS.PUBLIC, + level: PermissionLevel.READ, + resourceId: tableId, + }) + await config.api.permission.set({ + roleId: roles.BUILTIN_ROLE_IDS.POWER, + level: PermissionLevel.READ, + resourceId: viewId, + }) + await config.publish() + + await config.api.viewV2.search(viewId, undefined, { + usePublicUser: true, + expectStatus: 403, + }) + }) + }) }) }) }) diff --git a/packages/server/src/api/routes/tests/table.spec.ts b/packages/server/src/api/routes/tests/table.spec.ts index 04911e5505..9914e6d66f 100644 --- a/packages/server/src/api/routes/tests/table.spec.ts +++ b/packages/server/src/api/routes/tests/table.spec.ts @@ -87,7 +87,7 @@ describe("/tables", () => { it("updates all the row fields for a table when a schema key is renamed", async () => { const testTable = await config.createTable() - await config.createView({ + await config.createLegacyView({ name: "TestView", field: "Price", calculation: "stats", @@ -254,7 +254,7 @@ describe("/tables", () => { })) await config.api.viewV2.create({ tableId }) - await config.createView({ tableId, name: generator.guid() }) + await config.createLegacyView({ tableId, name: generator.guid() }) const res = await config.api.table.fetch() diff --git a/packages/server/src/api/routes/tests/view.spec.js b/packages/server/src/api/routes/tests/view.spec.js index df07ffa4af..e633279058 100644 --- a/packages/server/src/api/routes/tests/view.spec.js +++ b/packages/server/src/api/routes/tests/view.spec.js @@ -249,7 +249,7 @@ describe("/views", () => { }) it("returns only custom views", async () => { - await config.createView({ + await config.createLegacyView({ name: "TestView", field: "Price", calculation: "stats", @@ -267,7 +267,7 @@ describe("/views", () => { describe("query", () => { it("returns data for the created view", async () => { - await config.createView({ + await config.createLegacyView({ name: "TestView", field: "Price", calculation: "stats", @@ -295,7 +295,7 @@ describe("/views", () => { }) it("returns data for the created view using a group by", async () => { - await config.createView({ + await config.createLegacyView({ calculation: "stats", name: "TestView", field: "Price", @@ -331,7 +331,7 @@ describe("/views", () => { describe("destroy", () => { it("should be able to delete a view", async () => { const table = await config.createTable(priceTable()) - const view = await config.createView() + const view = await config.createLegacyView() const res = await request .delete(`/api/views/${view.name}`) .set(config.defaultHeaders()) @@ -395,7 +395,7 @@ describe("/views", () => { it("should be able to export a view as JSON", async () => { let table = await setupExport() - const view = await config.createView() + const view = await config.createLegacyView() table = await config.getTable(table._id) let res = await exportView(view.name, "json") @@ -407,7 +407,7 @@ describe("/views", () => { it("should be able to export a view as CSV", async () => { let table = await setupExport() - const view = await config.createView() + const view = await config.createLegacyView() table = await config.getTable(table._id) let res = await exportView(view.name, "csv") diff --git a/packages/server/src/api/routes/tests/viewV2.spec.ts b/packages/server/src/api/routes/tests/viewV2.spec.ts index a9c9f3a320..5e8ae09e55 100644 --- a/packages/server/src/api/routes/tests/viewV2.spec.ts +++ b/packages/server/src/api/routes/tests/viewV2.spec.ts @@ -296,7 +296,7 @@ describe.each([ }) it("cannot update views v1", async () => { - const viewV1 = await config.createView() + const viewV1 = await config.createLegacyView() await config.api.viewV2.update( { ...viewV1, diff --git a/packages/server/src/api/routes/view.ts b/packages/server/src/api/routes/view.ts index e09ecba679..545d3016a3 100644 --- a/packages/server/src/api/routes/view.ts +++ b/packages/server/src/api/routes/view.ts @@ -1,7 +1,7 @@ import Router from "@koa/router" import * as viewController from "../controllers/view" import * as rowController from "../controllers/row" -import authorized from "../../middleware/authorized" +import authorized, { authorizedResource } from "../../middleware/authorized" import { paramResource } from "../../middleware/resourceId" import { permissions } from "@budibase/backend-core" @@ -10,10 +10,10 @@ const router: Router = new Router() router .get( "/api/v2/views/:viewId", - paramResource("viewId"), - authorized( - permissions.PermissionType.TABLE, - permissions.PermissionLevel.READ + authorizedResource( + permissions.PermissionType.VIEW, + permissions.PermissionLevel.READ, + "viewId" ), viewController.v2.get ) diff --git a/packages/server/src/automations/triggers.ts b/packages/server/src/automations/triggers.ts index 9cc8c6b077..922bc10343 100644 --- a/packages/server/src/automations/triggers.ts +++ b/packages/server/src/automations/triggers.ts @@ -6,11 +6,11 @@ import { isDevAppID } from "../db/utils" // need this to call directly, so we can get a response import { automationQueue } from "./bullboard" import { checkTestFlag } from "../utilities/redis" +import * as utils from "./utils" import env from "../environment" import { context, db as dbCore } from "@budibase/backend-core" import { Automation, Row, AutomationData, AutomationJob } from "@budibase/types" import { executeSynchronously } from "../threads/automation" -import sdk from "../sdk" export const TRIGGER_DEFINITIONS = definitions const JOB_OPTS = { @@ -142,7 +142,7 @@ export async function rebootTrigger() { let automations = await getAllAutomations() let rebootEvents = [] for (let automation of automations) { - if (sdk.automations.isReboot(automation)) { + if (utils.isRebootTrigger(automation)) { const job = { automation, event: { diff --git a/packages/server/src/automations/utils.ts b/packages/server/src/automations/utils.ts index 18d2d30f82..9522ad6ccd 100644 --- a/packages/server/src/automations/utils.ts +++ b/packages/server/src/automations/utils.ts @@ -17,17 +17,16 @@ import { import sdk from "../sdk" import { automationsEnabled } from "../features" +const REBOOT_CRON = "@reboot" const WH_STEP_ID = definitions.WEBHOOK.stepId +const CRON_STEP_ID = definitions.CRON.stepId let Runner: Thread if (automationsEnabled()) { Runner = new Thread(ThreadType.AUTOMATION) } -function loggingArgs( - job: AutomationJob, - timing?: { start: number; complete?: boolean } -) { - const logs: any[] = [ +function loggingArgs(job: AutomationJob) { + return [ { _logKey: "automation", trigger: job.data.automation.definition.trigger.event, @@ -37,53 +36,24 @@ function loggingArgs( jobId: job.id, }, ] - if (timing?.start) { - logs.push({ - _logKey: "startTime", - start: timing.start, - }) - } - if (timing?.start && timing?.complete) { - const end = new Date().getTime() - const duration = end - timing.start - logs.push({ - _logKey: "endTime", - end, - }) - logs.push({ - _logKey: "duration", - duration, - }) - } - return logs } export async function processEvent(job: AutomationJob) { const appId = job.data.event.appId! const automationId = job.data.automation._id! - const start = new Date().getTime() const task = async () => { try { // need to actually await these so that an error can be captured properly - console.log("automation running", ...loggingArgs(job, { start })) + console.log("automation running", ...loggingArgs(job)) const runFn = () => Runner.run(job) const result = await quotas.addAutomation(runFn, { automationId, }) - const end = new Date().getTime() - const duration = end - start - console.log( - "automation completed", - ...loggingArgs(job, { start, complete: true }) - ) + console.log("automation completed", ...loggingArgs(job)) return result } catch (err) { - console.error( - `automation was unable to run`, - err, - ...loggingArgs(job, { start, complete: true }) - ) + console.error(`automation was unable to run`, err, ...loggingArgs(job)) return { err } } } @@ -163,6 +133,19 @@ export async function clearMetadata() { await db.bulkDocs(automationMetadata) } +export function isCronTrigger(auto: Automation) { + return ( + auto && + auto.definition.trigger && + auto.definition.trigger.stepId === CRON_STEP_ID + ) +} + +export function isRebootTrigger(auto: Automation) { + const trigger = auto ? auto.definition.trigger : null + return isCronTrigger(auto) && trigger?.inputs.cron === REBOOT_CRON +} + /** * This function handles checking of any cron jobs that need to be enabled/updated. * @param {string} appId The ID of the app in which we are checking for webhooks @@ -170,14 +153,14 @@ export async function clearMetadata() { */ export async function enableCronTrigger(appId: any, automation: Automation) { const trigger = automation ? automation.definition.trigger : null - const validCron = sdk.automations.isCron(automation) && trigger?.inputs.cron - const needsCreated = - !sdk.automations.isReboot(automation) && - !sdk.automations.disabled(automation) let enabled = false // need to create cron job - if (validCron && needsCreated) { + if ( + isCronTrigger(automation) && + !isRebootTrigger(automation) && + trigger?.inputs.cron + ) { // make a job id rather than letting Bull decide, makes it easier to handle on way out const jobId = `${appId}_cron_${newid()}` const job: any = await automationQueue.add( diff --git a/packages/server/src/middleware/authorized.ts b/packages/server/src/middleware/authorized.ts index 35d373efbf..b2ffeacaf8 100644 --- a/packages/server/src/middleware/authorized.ts +++ b/packages/server/src/middleware/authorized.ts @@ -6,11 +6,10 @@ import { users, } from "@budibase/backend-core" import { PermissionLevel, PermissionType, Role, UserCtx } from "@budibase/types" -import { features } from "@budibase/pro" import builderMiddleware from "./builder" import { isWebhookEndpoint } from "./utils" import { paramResource } from "./resourceId" -import { extractViewInfoFromID, isViewID } from "../db/utils" +import sdk from "../sdk" function hasResource(ctx: any) { return ctx.resourceId != null @@ -77,31 +76,6 @@ const checkAuthorizedResource = async ( } } -const resourceIdTranformers: Partial< - Record Promise> -> = { - [PermissionType.VIEW]: async ctx => { - const { resourceId } = ctx - if (!resourceId) { - ctx.throw(400, `Cannot obtain the view id`) - return - } - - if (!isViewID(resourceId)) { - ctx.throw(400, `"${resourceId}" is not a valid view id`) - return - } - - if (await features.isViewPermissionEnabled()) { - ctx.subResourceId = ctx.resourceId - ctx.resourceId = extractViewInfoFromID(resourceId).tableId - } else { - ctx.resourceId = extractViewInfoFromID(resourceId).tableId - delete ctx.subResourceId - } - }, -} - const authorized = ( permType: PermissionType, @@ -121,8 +95,8 @@ const authorized = } // get the resource roles - let resourceRoles: any = [] - let otherLevelRoles: any = [] + let resourceRoles: string[] = [] + let otherLevelRoles: string[] = [] const otherLevel = permLevel === PermissionLevel.READ ? PermissionLevel.WRITE @@ -133,21 +107,28 @@ const authorized = paramResource(resourcePath)(ctx, () => {}) } - if (resourceIdTranformers[permType]) { - await resourceIdTranformers[permType]!(ctx) - } - if (hasResource(ctx)) { const { resourceId, subResourceId } = ctx - resourceRoles = await roles.getRequiredResourceRole(permLevel!, { - resourceId, - subResourceId, - }) + + const permissions = await sdk.permissions.getResourcePerms(resourceId) + const subPermissions = + !!subResourceId && + (await sdk.permissions.getResourcePerms(subResourceId)) + + function getPermLevel(permLevel: string) { + let result: string[] = [] + if (permissions[permLevel]) { + result.push(permissions[permLevel].role) + } + if (subPermissions && subPermissions[permLevel]) { + result.push(subPermissions[permLevel].role) + } + return result + } + + resourceRoles = getPermLevel(permLevel!) if (opts && opts.schema) { - otherLevelRoles = await roles.getRequiredResourceRole(otherLevel, { - resourceId, - subResourceId, - }) + otherLevelRoles = getPermLevel(otherLevel!) } } diff --git a/packages/server/src/middleware/tests/authorized.spec.ts b/packages/server/src/middleware/tests/authorized.spec.ts index d47430e63f..0741f50e4d 100644 --- a/packages/server/src/middleware/tests/authorized.spec.ts +++ b/packages/server/src/middleware/tests/authorized.spec.ts @@ -1,28 +1,20 @@ -jest.mock("@budibase/backend-core", () => ({ - ...jest.requireActual("@budibase/backend-core"), - roles: { - ...jest.requireActual("@budibase/backend-core").roles, - getRequiredResourceRole: jest.fn().mockResolvedValue([]), - }, -})) -jest.mock("../../environment", () => ({ - prod: false, - isTest: () => true, - // @ts-ignore - isProd: () => this.prod, - _set: function (_key: string, value: string) { - this.prod = value === "production" - }, +jest.mock("../../sdk/app/permissions", () => ({ + ...jest.requireActual("../../sdk/app/permissions"), + getResourcePerms: jest.fn().mockResolvedValue([]), })) -import { PermissionType, PermissionLevel } from "@budibase/types" +import { + PermissionType, + PermissionLevel, + PermissionSource, +} from "@budibase/types" import authorizedMiddleware from "../authorized" import env from "../../environment" import { generateTableID, generateViewID } from "../../db/utils" -import { roles } from "@budibase/backend-core" -import { mocks } from "@budibase/backend-core/tests" +import { generator, mocks } from "@budibase/backend-core/tests" import { initProMocks } from "../../tests/utilities/mocks/pro" +import { getResourcePerms } from "../../sdk/app/permissions" const APP_ID = "" @@ -189,23 +181,26 @@ describe("Authorization middleware", () => { ) }) - describe("view type", () => { - const tableId = generateTableID() - const viewId = generateViewID(tableId) - - const mockedGetRequiredResourceRole = - roles.getRequiredResourceRole as jest.MockedFunction< - typeof roles.getRequiredResourceRole - > + describe("with resource", () => { + let resourceId: string + const mockedGetResourcePerms = getResourcePerms as jest.MockedFunction< + typeof getResourcePerms + > beforeEach(() => { config.setMiddlewareRequiredPermission( PermissionType.VIEW, PermissionLevel.READ ) - config.setResourceId(viewId) + resourceId = generator.guid() + config.setResourceId(resourceId) - mockedGetRequiredResourceRole.mockResolvedValue(["PUBLIC"]) + mockedGetResourcePerms.mockResolvedValue({ + [PermissionLevel.READ]: { + role: "PUBLIC", + type: PermissionSource.BASE, + }, + }) config.setUser({ _id: "user", @@ -215,57 +210,14 @@ describe("Authorization middleware", () => { }) }) - it("will ignore view permissions if flag is off", async () => { + it("will fetch resource permissions when resource is set", async () => { await config.executeMiddleware() expect(config.throw).not.toBeCalled() expect(config.next).toHaveBeenCalled() - expect(mockedGetRequiredResourceRole).toBeCalledTimes(1) - expect(mockedGetRequiredResourceRole).toBeCalledWith( - PermissionLevel.READ, - expect.objectContaining({ - resourceId: tableId, - subResourceId: undefined, - }) - ) - }) - - it("will use view permissions if flag is on", async () => { - mocks.licenses.useViewPermissions() - await config.executeMiddleware() - - expect(config.throw).not.toBeCalled() - expect(config.next).toHaveBeenCalled() - - expect(mockedGetRequiredResourceRole).toBeCalledTimes(1) - expect(mockedGetRequiredResourceRole).toBeCalledWith( - PermissionLevel.READ, - expect.objectContaining({ - resourceId: tableId, - subResourceId: viewId, - }) - ) - }) - - it("throw an exception if the resource id is not provided", async () => { - config.setResourceId(undefined) - await config.executeMiddleware() - expect(config.throw).toHaveBeenNthCalledWith( - 1, - 400, - "Cannot obtain the view id" - ) - }) - - it("throw an exception if the resource id is not a valid view id", async () => { - config.setResourceId(tableId) - await config.executeMiddleware() - expect(config.throw).toHaveBeenNthCalledWith( - 1, - 400, - `"${tableId}" is not a valid view id` - ) + expect(mockedGetResourcePerms).toBeCalledTimes(1) + expect(mockedGetResourcePerms).toBeCalledWith(resourceId) }) }) }) diff --git a/packages/server/src/migrations/tests/index.spec.ts b/packages/server/src/migrations/tests/index.spec.ts index 2465c930b4..b64cad26c1 100644 --- a/packages/server/src/migrations/tests/index.spec.ts +++ b/packages/server/src/migrations/tests/index.spec.ts @@ -50,9 +50,9 @@ describe("migrations", () => { await config.createRole() await config.createRole() await config.createTable() - await config.createView() + await config.createLegacyView() await config.createTable() - await config.createView(structures.view(config.table!._id!)) + await config.createLegacyView(structures.view(config.table!._id!)) await config.createScreen() await config.createScreen() diff --git a/packages/server/src/sdk/app/automations/automations.ts b/packages/server/src/sdk/app/automations/automations.ts deleted file mode 100644 index 80f84c90e2..0000000000 --- a/packages/server/src/sdk/app/automations/automations.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { context } from "@budibase/backend-core" -import { Automation, AutomationState, DocumentType } from "@budibase/types" -import { definitions } from "../../../automations/triggerInfo" - -const REBOOT_CRON = "@reboot" - -export async function exists(automationId: string) { - if (!automationId?.startsWith(DocumentType.AUTOMATION)) { - throw new Error("Invalid automation ID.") - } - const db = context.getAppDB() - return db.docExists(automationId) -} - -export async function get(automationId: string) { - const db = context.getAppDB() - return (await db.get(automationId)) as Automation -} - -export function disabled(automation: Automation) { - return automation.state === AutomationState.DISABLED || !hasSteps(automation) -} - -export function isCron(automation: Automation) { - return ( - automation?.definition.trigger && - automation?.definition.trigger.stepId === definitions.CRON.stepId - ) -} - -export function isReboot(automation: Automation) { - const trigger = automation?.definition.trigger - return isCron(automation) && trigger?.inputs.cron === REBOOT_CRON -} - -export function hasSteps(automation: Automation) { - return automation?.definition?.steps?.length > 0 -} diff --git a/packages/server/src/sdk/app/automations/index.ts b/packages/server/src/sdk/app/automations/index.ts index 540d5545fc..16530cf085 100644 --- a/packages/server/src/sdk/app/automations/index.ts +++ b/packages/server/src/sdk/app/automations/index.ts @@ -1,9 +1,7 @@ import * as webhook from "./webhook" import * as utils from "./utils" -import * as automations from "./automations" export default { webhook, utils, - ...automations, } diff --git a/packages/server/src/sdk/app/backups/exports.ts b/packages/server/src/sdk/app/backups/exports.ts index 306918cf80..307cdf4015 100644 --- a/packages/server/src/sdk/app/backups/exports.ts +++ b/packages/server/src/sdk/app/backups/exports.ts @@ -60,7 +60,7 @@ function tarFilesToTmp(tmpDir: string, files: string[]) { export async function exportDB( dbName: string, opts: DBDumpOpts = {} -): Promise { +): Promise { const exportOpts = { filter: opts?.filter, batch_size: 1000, diff --git a/packages/server/src/sdk/app/permissions/index.ts b/packages/server/src/sdk/app/permissions/index.ts index 2219120db6..53f6756ae1 100644 --- a/packages/server/src/sdk/app/permissions/index.ts +++ b/packages/server/src/sdk/app/permissions/index.ts @@ -1,10 +1,24 @@ +import { context, db, env, roles } from "@budibase/backend-core" +import { features } from "@budibase/pro" import { DocumentType, PermissionLevel, + PermissionSource, + PlanType, + Role, VirtualDocumentType, } from "@budibase/types" -import { isViewID } from "../../../db/utils" -import { features } from "@budibase/pro" +import { + extractViewInfoFromID, + getRoleParams, + isViewID, +} from "../../../db/utils" +import { + CURRENTLY_SUPPORTED_LEVELS, + getBasePermissions, +} from "../../../utilities/security" +import sdk from "../../../sdk" +import { isV2 } from "../views" type ResourceActionAllowedResult = | { allowed: true } @@ -35,3 +49,117 @@ export async function resourceActionAllowed({ resourceType: VirtualDocumentType.VIEW, } } + +type ResourcePermissions = Record< + string, + { role: string; type: PermissionSource } +> + +export async function getInheritablePermissions( + resourceId: string +): Promise { + if (isViewID(resourceId)) { + return await getResourcePerms(extractViewInfoFromID(resourceId).tableId) + } +} + +export async function allowsExplicitPermissions(resourceId: string) { + if (isViewID(resourceId)) { + const allowed = await features.isViewPermissionEnabled() + const minPlan = !allowed + ? env.SELF_HOSTED + ? PlanType.BUSINESS + : PlanType.PREMIUM + : undefined + + return { + allowed, + minPlan, + } + } + + return { allowed: true } +} + +export async function getResourcePerms( + resourceId: string +): Promise { + const db = context.getAppDB() + const body = await db.allDocs( + getRoleParams(null, { + include_docs: true, + }) + ) + const rolesList = body.rows.map(row => row.doc) + let permissions: ResourcePermissions = {} + + const permsToInherit = await getInheritablePermissions(resourceId) + + const allowsExplicitPerm = (await allowsExplicitPermissions(resourceId)) + .allowed + + for (let level of CURRENTLY_SUPPORTED_LEVELS) { + // update the various roleIds in the resource permissions + for (let role of rolesList) { + const rolePerms = allowsExplicitPerm + ? roles.checkForRoleResourceArray(role.permissions, resourceId) + : {} + if (rolePerms[resourceId]?.indexOf(level) > -1) { + permissions[level] = { + role: roles.getExternalRoleID(role._id!, role.version), + type: PermissionSource.EXPLICIT, + } + } else if ( + !permissions[level] && + permsToInherit && + permsToInherit[level] + ) { + permissions[level] = { + role: permsToInherit[level].role, + type: PermissionSource.INHERITED, + } + } + } + } + + const basePermissions = Object.entries( + getBasePermissions(resourceId) + ).reduce((p, [level, role]) => { + p[level] = { role, type: PermissionSource.BASE } + return p + }, {}) + const result = Object.assign(basePermissions, permissions) + return result +} + +export async function getDependantResources( + resourceId: string +): Promise | undefined> { + if (db.isTableId(resourceId)) { + const dependants: Record> = {} + + const table = await sdk.tables.getTable(resourceId) + const views = Object.values(table.views || {}) + + for (const view of views) { + if (!isV2(view)) { + continue + } + + const permissions = await getResourcePerms(view.id) + for (const [level, roleInfo] of Object.entries(permissions)) { + if (roleInfo.type === PermissionSource.INHERITED) { + dependants[VirtualDocumentType.VIEW] ??= new Set() + dependants[VirtualDocumentType.VIEW].add(view.id) + } + } + } + + return Object.entries(dependants).reduce((p, [type, resources]) => { + p[type] = resources.size + return p + }, {} as Record) + } + + return +} diff --git a/packages/server/src/tests/utilities/TestConfiguration.ts b/packages/server/src/tests/utilities/TestConfiguration.ts index c1db54fe60..81bfa0abbd 100644 --- a/packages/server/src/tests/utilities/TestConfiguration.ts +++ b/packages/server/src/tests/utilities/TestConfiguration.ts @@ -622,7 +622,7 @@ class TestConfiguration { // VIEW - async createView(config?: any) { + async createLegacyView(config?: any) { if (!this.table) { throw "Test requires table to be configured." } diff --git a/packages/server/src/threads/automation.ts b/packages/server/src/threads/automation.ts index fc64e206d6..8bdf9cbe65 100644 --- a/packages/server/src/threads/automation.ts +++ b/packages/server/src/threads/automation.ts @@ -1,6 +1,5 @@ import { default as threadUtils } from "./utils" import { Job } from "bull" -threadUtils.threadSetup() import { disableCronById, isErrorInOutput, @@ -35,8 +34,8 @@ import { cloneDeep } from "lodash/fp" import { performance } from "perf_hooks" import * as sdkUtils from "../sdk/utils" import env from "../environment" -import sdk from "../sdk" +threadUtils.threadSetup() const FILTER_STEP_ID = actions.BUILTIN_ACTION_DEFINITIONS.FILTER.stepId const LOOP_STEP_ID = actions.BUILTIN_ACTION_DEFINITIONS.LOOP.stepId const CRON_STEP_ID = triggerDefs.CRON.stepId @@ -520,8 +519,7 @@ class Orchestrator { export function execute(job: Job, callback: WorkerCallback) { const appId = job.data.event.appId - const automation = job.data.automation - const automationId = automation._id + const automationId = job.data.automation._id if (!appId) { throw new Error("Unable to execute, event doesn't contain app ID.") } @@ -532,30 +530,10 @@ export function execute(job: Job, callback: WorkerCallback) { appId, automationId, task: async () => { - let automation = job.data.automation, - isCron = sdk.automations.isCron(job.data.automation), - notFound = false - try { - automation = await sdk.automations.get(automationId) - } catch (err: any) { - // automation no longer exists - notFound = err - } - const disabled = sdk.automations.disabled(automation) - const stopAutomation = disabled || notFound const envVars = await sdkUtils.getEnvironmentVariables() // put into automation thread for whole context await context.doInEnvironmentContext(envVars, async () => { const automationOrchestrator = new Orchestrator(job) - // hard stop on automations - if (isCron && stopAutomation) { - await automationOrchestrator.stopCron( - disabled ? "disabled" : "not_found" - ) - } - if (stopAutomation) { - return - } try { const response = await automationOrchestrator.execute() callback(null, response) diff --git a/packages/server/src/utilities/security.ts b/packages/server/src/utilities/security.ts index 0da7621773..01a3468c9c 100644 --- a/packages/server/src/utilities/security.ts +++ b/packages/server/src/utilities/security.ts @@ -23,6 +23,9 @@ export function getPermissionType(resourceId: string) { case DocumentType.QUERY: case DocumentType.DATASOURCE: return permissions.PermissionType.QUERY + default: + // legacy views don't have an ID, will end up here + return permissions.PermissionType.LEGACY_VIEW } } diff --git a/packages/types/src/api/web/app/index.ts b/packages/types/src/api/web/app/index.ts index e7b4b87aa9..276d7fa7c1 100644 --- a/packages/types/src/api/web/app/index.ts +++ b/packages/types/src/api/web/app/index.ts @@ -4,3 +4,4 @@ export * from "./row" export * from "./view" export * from "./rows" export * from "./table" +export * from "./permission" diff --git a/packages/types/src/api/web/app/permission.ts b/packages/types/src/api/web/app/permission.ts new file mode 100644 index 0000000000..a8ab0e8084 --- /dev/null +++ b/packages/types/src/api/web/app/permission.ts @@ -0,0 +1,16 @@ +import { PlanType } from "../../../sdk" + +export interface ResourcePermissionInfo { + role: string + permissionType: string + inheritablePermission?: string +} + +export interface GetResourcePermsResponse { + permissions: Record + requiresPlanToModify?: PlanType +} + +export interface GetDependantResourcesResponse { + resourceByType?: Record +} diff --git a/packages/types/src/documents/app/automation.ts b/packages/types/src/documents/app/automation.ts index f4a0dd33f2..88ce5e9b9a 100644 --- a/packages/types/src/documents/app/automation.ts +++ b/packages/types/src/documents/app/automation.ts @@ -100,10 +100,6 @@ export const AutomationStepIdArray = [ ...Object.values(AutomationTriggerStepId), ] -export enum AutomationState { - DISABLED = "disabled", -} - export interface Automation extends Document { definition: { steps: AutomationStep[] @@ -116,7 +112,6 @@ export interface Automation extends Document { name: string internal?: boolean type?: string - state?: AutomationState } interface BaseIOStructure { diff --git a/packages/types/src/sdk/db.ts b/packages/types/src/sdk/db.ts index 9ac8ff26d4..e07cc82fd2 100644 --- a/packages/types/src/sdk/db.ts +++ b/packages/types/src/sdk/db.ts @@ -40,11 +40,6 @@ export type DatabasePutOpts = { force?: boolean } -export type DocExistsResponse = { - _rev?: string - exists: boolean -} - export type DatabaseCreateIndexOpts = { index: { fields: string[] @@ -95,7 +90,6 @@ export interface Database { exists(): Promise checkSetup(): Promise> get(id?: string): Promise - docExists(id: string): Promise remove( id: string | Document, rev?: string diff --git a/packages/types/src/sdk/permissions.ts b/packages/types/src/sdk/permissions.ts index a33d4985ee..c855bbd219 100644 --- a/packages/types/src/sdk/permissions.ts +++ b/packages/types/src/sdk/permissions.ts @@ -16,4 +16,11 @@ export enum PermissionType { GLOBAL_BUILDER = "globalBuilder", QUERY = "query", VIEW = "view", + LEGACY_VIEW = "legacy_view", +} + +export enum PermissionSource { + EXPLICIT = "EXPLICIT", + INHERITED = "INHERITED", + BASE = "BASE", } diff --git a/packages/worker/src/api/controllers/global/users.ts b/packages/worker/src/api/controllers/global/users.ts index 0ccf9a356f..6d3a13aa4e 100644 --- a/packages/worker/src/api/controllers/global/users.ts +++ b/packages/worker/src/api/controllers/global/users.ts @@ -266,17 +266,14 @@ export const onboardUsers = async (ctx: Ctx) => { // Temp password to be passed to the user. createdPasswords[invite.email] = password - let builder: { global: boolean; apps?: string[] } = { global: false } - if (invite.userInfo.appBuilders) { - builder.apps = invite.userInfo.appBuilders - } + return { email: invite.email, password, forceResetPassword: true, roles: invite.userInfo.apps, - admin: { global: false }, - builder, + admin: invite.userInfo.admin, + builder: invite.userInfo.builder, tenantId: tenancy.getTenantId(), } }) @@ -371,13 +368,10 @@ export const updateInvite = async (ctx: any) => { ...invite, } - if (!updateBody?.appBuilders || !updateBody.appBuilders?.length) { - updated.info.appBuilders = [] - } else { - updated.info.appBuilders = [ - ...(invite.info.appBuilders ?? []), - ...updateBody.appBuilders, - ] + if (!updateBody?.builder?.apps && updated.info?.builder?.apps) { + updated.info.builder.apps = [] + } else if (updateBody?.builder) { + updated.info.builder = updateBody.builder } if (!updateBody?.apps || !Object.keys(updateBody?.apps).length) { @@ -409,15 +403,17 @@ export const inviteAccept = async ( lastName, password, email, + admin: { global: info?.admin?.global || false }, roles: info.apps, tenantId: info.tenantId, } - let builder: { global: boolean; apps?: string[] } = { global: false } + let builder: { global: boolean; apps?: string[] } = { + global: info?.builder?.global || false, + } - if (info.appBuilders) { - builder.apps = info.appBuilders + if (info?.builder?.apps) { + builder.apps = info.builder.apps request.builder = builder - delete info.appBuilders } delete info.apps request = { diff --git a/scripts/build.js b/scripts/build.js index 0175388051..8356f2e035 100755 --- a/scripts/build.js +++ b/scripts/build.js @@ -15,7 +15,7 @@ const { nodeExternalsPlugin } = require("esbuild-node-externals") var argv = require("minimist")(process.argv.slice(2)) -function runBuild(entry, outfile) { +function runBuild(entry, outfile, opts = { skipMeta: false, bundle: true }) { const isDev = process.env.NODE_ENV !== "production" const tsconfig = argv["p"] || `tsconfig.build.json` const tsconfigPathPluginContent = JSON.parse( @@ -36,12 +36,16 @@ function runBuild(entry, outfile) { ] } + const metafile = !opts.skipMeta + const { bundle } = opts + const sharedConfig = { entryPoints: [entry], - bundle: true, + bundle, minify: !isDev, sourcemap: isDev, tsconfig, + format: opts?.forcedFormat, plugins: [ TsconfigPathsPlugin({ tsconfig: tsconfigPathPluginContent }), nodeExternalsPlugin(), @@ -50,15 +54,10 @@ function runBuild(entry, outfile) { loader: { ".svelte": "copy", }, - metafile: true, - external: [ - "deasync", - "mock-aws-s3", - "nock", - "pino", - "koa-pino-logger", - "bull", - ], + metafile, + external: bundle + ? ["deasync", "mock-aws-s3", "nock", "pino", "koa-pino-logger", "bull"] + : undefined, } build({ @@ -77,10 +76,12 @@ function runBuild(entry, outfile) { ) }) - fs.writeFileSync( - `dist/${path.basename(outfile)}.meta.json`, - JSON.stringify(result.metafile) - ) + if (metafile) { + fs.writeFileSync( + `dist/${path.basename(outfile)}.meta.json`, + JSON.stringify(result.metafile) + ) + } }) }