diff --git a/packages/builder/cypress/integration/createTable.spec.js b/packages/builder/cypress/integration/createTable.spec.js index fdad39b956..27560aaeb9 100644 --- a/packages/builder/cypress/integration/createTable.spec.js +++ b/packages/builder/cypress/integration/createTable.spec.js @@ -60,7 +60,7 @@ context("Create a Table", () => { }) it("deletes a table", () => { - cy.contains(".nav-item", "dog").get(".actions").invoke("show").click() + cy.get(".actions").first().invoke("show").click() cy.get("[data-cy=delete-table]").click() cy.contains("Delete Table").click() cy.contains("dog").should("not.exist") diff --git a/packages/builder/cypress/integration/createUser.spec.js b/packages/builder/cypress/integration/createUser.spec.js index b1f737068b..cbde6179b2 100644 --- a/packages/builder/cypress/integration/createUser.spec.js +++ b/packages/builder/cypress/integration/createUser.spec.js @@ -9,9 +9,9 @@ context('Create a User', () => { // https://on.cypress.io/interacting-with-elements it('should create a user', () => { - cy.createUser('bbuser', 'test', 'POWER_USER') + cy.createUser("bbuser", "test", "ADMIN") - // Check to make sure user was created! - cy.get("input[disabled]").should('have.value', 'bbuser') + // // Check to make sure user was created! + cy.contains("bbuser").should('be.visible') }) }) diff --git a/packages/builder/cypress/support/commands.js b/packages/builder/cypress/support/commands.js index 15564083ba..76f3417eac 100644 --- a/packages/builder/cypress/support/commands.js +++ b/packages/builder/cypress/support/commands.js @@ -113,23 +113,26 @@ Cypress.Commands.add("addRow", values => { Cypress.Commands.add("createUser", (username, password, accessLevel) => { // Create User - cy.get(".toprightnav > .settings").click() cy.contains("Users").click() - cy.get("[name=Name]") - .first() - .type(username) - cy.get("[name=Password]") - .first() - .type(password) - cy.get("select") - .first() - .select(accessLevel) + cy.contains("Create New Row").click() - // Save - cy.get(".inputs") - .contains("Create") - .click() + cy.get(".modal").within(() => { + cy.get("input") + .first() + .type(password) + cy.get("input") + .eq(1) + .type(username) + cy.get("select") + .first() + .select(accessLevel) + + // Save + cy.get(".buttons") + .contains("Create Row") + .click() + }) }) Cypress.Commands.add("addHeadlineComponent", text => { diff --git a/packages/builder/src/components/backend/DataTable/RowFieldControl.svelte b/packages/builder/src/components/backend/DataTable/RowFieldControl.svelte index f974e521c4..c21d3449b6 100644 --- a/packages/builder/src/components/backend/DataTable/RowFieldControl.svelte +++ b/packages/builder/src/components/backend/DataTable/RowFieldControl.svelte @@ -1,14 +1,19 @@ {#if type === 'options'} @@ -30,5 +35,11 @@ {:else if type === 'link'} {:else} - + {/if} diff --git a/packages/builder/src/components/backend/DataTable/api.js b/packages/builder/src/components/backend/DataTable/api.js index 496db35b3f..629405a9fc 100644 --- a/packages/builder/src/components/backend/DataTable/api.js +++ b/packages/builder/src/components/backend/DataTable/api.js @@ -7,8 +7,8 @@ export async function createUser(user) { } export async function saveRow(row, tableId) { - const SAVE_ROWS_URL = `/api/${tableId}/rows` - const response = await api.post(SAVE_ROWS_URL, row) + const SAVE_ROW_URL = `/api/${tableId}/rows` + const response = await api.post(SAVE_ROW_URL, row) return await response.json() } diff --git a/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte b/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte index 866e574bca..ee72b36053 100644 --- a/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte +++ b/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte @@ -2,6 +2,7 @@ import { Input, Button, TextButton, Select, Toggle } from "@budibase/bbui" import { cloneDeep } from "lodash/fp" import { backendUiStore } from "builderStore" + import { TableNames, UNEDITABLE_USER_FIELDS } from "constants" import { FIELDS } from "constants/backend" import { notifier } from "builderStore/store/notifications" import ValuesList from "components/common/ValuesList.svelte" @@ -30,6 +31,9 @@ table => table._id !== $backendUiStore.draftTable._id ) $: required = !!field?.constraints?.presence || primaryDisplay + $: uneditable = + $backendUiStore.selectedTable?._id === TableNames.USERS && + UNEDITABLE_USER_FIELDS.includes(field.name) async function saveColumn() { backendUiStore.update(state => { @@ -87,7 +91,7 @@
- + - - {#if editMode} - - {:else} - - {/if} -
- - diff --git a/packages/builder/src/components/settings/tabs/Users.svelte b/packages/builder/src/components/settings/tabs/Users.svelte deleted file mode 100644 index 3d4a44fc73..0000000000 --- a/packages/builder/src/components/settings/tabs/Users.svelte +++ /dev/null @@ -1,114 +0,0 @@ - - -
-
- -
- - - - -
-
-
- - {#await fetchUsersPromise} - Loading... - {:then users} -
    - {#each users as user} -
  • - -
  • - {:else} -
  • No Users found
  • - {/each} -
- {:catch err} - Something went wrong when trying to fetch users. Please refresh (CMD + R / - CTRL + R) the page and try again. - {/await} -
-
- - diff --git a/packages/builder/src/components/settings/tabs/index.js b/packages/builder/src/components/settings/tabs/index.js index 6e34141d09..2f0e958112 100644 --- a/packages/builder/src/components/settings/tabs/index.js +++ b/packages/builder/src/components/settings/tabs/index.js @@ -1,6 +1,5 @@ export { default as General } from "./General.svelte" export { default as Integrations } from "./Integrations.svelte" export { default as Permissions } from "./Permissions.svelte" -export { default as Users } from "./Users.svelte" export { default as APIKeys } from "./APIKeys.svelte" export { default as DangerZone } from "./DangerZone.svelte" diff --git a/packages/builder/src/constants/index.js b/packages/builder/src/constants/index.js index 65df616aec..ae88d81bde 100644 --- a/packages/builder/src/constants/index.js +++ b/packages/builder/src/constants/index.js @@ -1,3 +1,10 @@ +export const TableNames = { + USERS: "ta_users", +} + +// fields on the user table that cannot be edited +export const UNEDITABLE_USER_FIELDS = ["username", "password", "accessLevelId"] + export const DEFAULT_PAGES_OBJECT = { main: { props: { diff --git a/packages/builder/src/pages/[application]/_reset.svelte b/packages/builder/src/pages/[application]/_reset.svelte index 31aa6943e5..a6f7e86454 100644 --- a/packages/builder/src/pages/[application]/_reset.svelte +++ b/packages/builder/src/pages/[application]/_reset.svelte @@ -68,16 +68,11 @@
-
- - - -
diff --git a/packages/server/package.json b/packages/server/package.json index e4c0e8be32..cb1789c5c9 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -81,6 +81,7 @@ "lodash": "^4.17.13", "mustache": "^4.0.1", "node-fetch": "^2.6.0", + "open": "^7.3.0", "pino-pretty": "^4.0.0", "pouchdb": "^7.2.1", "pouchdb-all-dbs": "^1.0.2", diff --git a/packages/server/src/api/controllers/application.js b/packages/server/src/api/controllers/application.js index f05430e05a..2cbb84d240 100644 --- a/packages/server/src/api/controllers/application.js +++ b/packages/server/src/api/controllers/application.js @@ -26,6 +26,7 @@ const { const { MAIN, UNAUTHENTICATED, PageTypes } = require("../../constants/pages") const { HOME_SCREEN } = require("../../constants/screens") const { cloneDeep } = require("lodash/fp") +const { USERS_TABLE_SCHEMA } = require("../../constants") const APP_PREFIX = DocumentTypes.APP + SEPARATOR @@ -67,6 +68,9 @@ async function createInstance(template) { if (!ok) { throw "Error loading database dump from template." } + } else { + // create the users table + await db.put(USERS_TABLE_SCHEMA) } return { _id: appId } diff --git a/packages/server/src/api/controllers/row.js b/packages/server/src/api/controllers/row.js index ccf26b02e2..c7fb70d50e 100644 --- a/packages/server/src/api/controllers/row.js +++ b/packages/server/src/api/controllers/row.js @@ -6,7 +6,9 @@ const { generateRowID, DocumentTypes, SEPARATOR, + ViewNames, } = require("../../db/utils") +const usersController = require("./user") const { cloneDeep } = require("lodash") const TABLE_VIEW_BEGINS_WITH = `all${SEPARATOR}${DocumentTypes.TABLE}${SEPARATOR}` @@ -118,6 +120,16 @@ exports.save = async function(ctx) { table, }) + // Creation of a new user goes to the user controller + if (!existingRow && row.tableId === ViewNames.USERS) { + try { + await usersController.create(ctx) + } catch (err) { + ctx.body = { errors: [err.message] } + } + return + } + if (existingRow) { const response = await db.put(row) row._rev = response.rev @@ -315,8 +327,8 @@ exports.fetchEnrichedRow = async function(ctx) { ctx.status = 200 } -function coerceRowValues(rec, table) { - const row = cloneDeep(rec) +function coerceRowValues(record, table) { + const row = cloneDeep(record) for (let [key, value] of Object.entries(row)) { const field = table.schema[key] if (!field) continue diff --git a/packages/server/src/api/controllers/user.js b/packages/server/src/api/controllers/user.js index c51e1fd2b8..f67ae72a89 100644 --- a/packages/server/src/api/controllers/user.js +++ b/packages/server/src/api/controllers/user.js @@ -1,6 +1,6 @@ const CouchDB = require("../../db") const bcrypt = require("../../utilities/bcrypt") -const { generateUserID, getUserParams } = require("../../db/utils") +const { generateUserID, getUserParams, ViewNames } = require("../../db/utils") const { BUILTIN_LEVEL_ID_ARRAY, } = require("../../utilities/security/accessLevels") @@ -11,7 +11,7 @@ const { exports.fetch = async function(ctx) { const database = new CouchDB(ctx.user.appId) const data = await database.allDocs( - getUserParams(null, { + getUserParams("", { include_docs: true, }) ) @@ -44,6 +44,7 @@ exports.create = async function(ctx) { type: "user", accessLevelId, permissions: permissions || [BUILTIN_PERMISSION_NAMES.POWER], + tableId: ViewNames.USERS, } try { diff --git a/packages/server/src/automations/steps/createUser.js b/packages/server/src/automations/steps/createUser.js index 4b6250ce36..b24c2cbe56 100644 --- a/packages/server/src/automations/steps/createUser.js +++ b/packages/server/src/automations/steps/createUser.js @@ -28,7 +28,7 @@ module.exports.definition = { accessLevelId: { type: "string", title: "Access Level", - enum: accessLevels.BUILTIN_LEVEL_IDS, + enum: accessLevels.BUILTIN_LEVEL_ID_ARRAY, pretty: accessLevels.BUILTIN_LEVEL_NAME_ARRAY, }, }, diff --git a/packages/server/src/constants/index.js b/packages/server/src/constants/index.js index cae1cde3e8..24fe45eb6b 100644 --- a/packages/server/src/constants/index.js +++ b/packages/server/src/constants/index.js @@ -1,7 +1,42 @@ +const { BUILTIN_LEVEL_IDS } = require("../utilities/security/accessLevels") + const AuthTypes = { APP: "app", BUILDER: "builder", EXTERNAL: "external", } +const USERS_TABLE_SCHEMA = { + _id: "ta_users", + type: "table", + views: {}, + name: "Users", + schema: { + username: { + type: "string", + constraints: { + type: "string", + length: { + maximum: "", + }, + presence: true, + }, + fieldName: "username", + name: "username", + }, + accessLevelId: { + fieldName: "accessLevelId", + name: "accessLevelId", + type: "options", + constraints: { + type: "string", + presence: false, + inclusion: Object.keys(BUILTIN_LEVEL_IDS), + }, + }, + }, + primaryDisplay: "username", +} + exports.AuthTypes = AuthTypes +exports.USERS_TABLE_SCHEMA = USERS_TABLE_SCHEMA diff --git a/packages/server/src/db/utils.js b/packages/server/src/db/utils.js index 4edb27a416..96fd92218e 100644 --- a/packages/server/src/db/utils.js +++ b/packages/server/src/db/utils.js @@ -20,6 +20,7 @@ const DocumentTypes = { const ViewNames = { LINK: "by_link", ROUTING: "screen_routes", + USERS: "ta_users", } exports.ViewNames = ViewNames @@ -80,13 +81,12 @@ exports.generateTableID = () => { exports.getRowParams = (tableId = null, rowId = null, otherProps = {}) => { if (tableId == null) { return getDocParams(DocumentTypes.ROW, null, otherProps) - } else { - const endOfKey = - rowId == null - ? `${tableId}${SEPARATOR}` - : `${tableId}${SEPARATOR}${rowId}` - return getDocParams(DocumentTypes.ROW, endOfKey, otherProps) } + + const endOfKey = + rowId == null ? `${tableId}${SEPARATOR}` : `${tableId}${SEPARATOR}${rowId}` + + return getDocParams(DocumentTypes.ROW, endOfKey, otherProps) } /** @@ -101,8 +101,12 @@ exports.generateRowID = tableId => { /** * Gets parameters for retrieving users, this is a utility function for the getDocParams function. */ -exports.getUserParams = (username = null, otherProps = {}) => { - return getDocParams(DocumentTypes.USER, username, otherProps) +exports.getUserParams = (username = "", otherProps = {}) => { + return getDocParams( + DocumentTypes.ROW, + `${ViewNames.USERS}${SEPARATOR}${DocumentTypes.USER}${SEPARATOR}${username}`, + otherProps + ) } /** @@ -111,7 +115,7 @@ exports.getUserParams = (username = null, otherProps = {}) => { * @returns {string} The new user ID which the user doc can be stored under. */ exports.generateUserID = username => { - return `${DocumentTypes.USER}${SEPARATOR}${username}` + return `${DocumentTypes.ROW}${SEPARATOR}${ViewNames.USERS}${SEPARATOR}${DocumentTypes.USER}${SEPARATOR}${username}` } /** diff --git a/packages/server/yarn.lock b/packages/server/yarn.lock index c31727265a..a1049034c2 100644 --- a/packages/server/yarn.lock +++ b/packages/server/yarn.lock @@ -3868,6 +3868,11 @@ is-descriptor@^1.0.0, is-descriptor@^1.0.2: is-data-descriptor "^1.0.0" kind-of "^6.0.2" +is-docker@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.1.1.tgz#4125a88e44e450d384e09047ede71adc2d144156" + integrity sha512-ZOoqiXfEwtGknTiuDEy8pN2CfE3TxMHprvNer1mXiqwkOT77Rw3YVrUQ52EqAOU3QAWDQ+bQdx7HJzrv7LS2Hw== + is-extendable@^0.1.0, is-extendable@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89" @@ -4027,6 +4032,13 @@ is-wsl@^1.1.0: resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-1.1.0.tgz#1f16e4aa22b04d1336b66188a66af3c600c3a66d" integrity sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0= +is-wsl@^2.1.1: + version "2.2.0" + resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271" + integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww== + dependencies: + is-docker "^2.0.0" + is-yarn-global@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/is-yarn-global/-/is-yarn-global-0.3.0.tgz#d502d3382590ea3004893746754c89139973e232" @@ -5648,6 +5660,14 @@ only@~0.0.2: resolved "https://registry.yarnpkg.com/only/-/only-0.0.2.tgz#2afde84d03e50b9a8edc444e30610a70295edfb4" integrity sha1-Kv3oTQPlC5qO3EROMGEKcCle37Q= +open@^7.3.0: + version "7.3.0" + resolved "https://registry.yarnpkg.com/open/-/open-7.3.0.tgz#45461fdee46444f3645b6e14eb3ca94b82e1be69" + integrity sha512-mgLwQIx2F/ye9SmbrUkurZCnkoXyXyu9EbHtJZrICjVAJfyMArdHp3KkixGdZx1ZHFPNIwl0DDM1dFFqXbTLZw== + dependencies: + is-docker "^2.0.0" + is-wsl "^2.1.1" + optionator@^0.8.1, optionator@^0.8.3: version "0.8.3" resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495"