From f05d696ef7e6d04889524749dce6463b8ae9bb04 Mon Sep 17 00:00:00 2001 From: Martin McKeaveney Date: Fri, 4 Dec 2020 12:22:45 +0000 Subject: [PATCH 1/5] email as default user identifier --- packages/builder/cypress/support/commands.js | 8 ++--- .../SetupPanel/AutomationBlockSetup.svelte | 2 ++ .../components/start/CreateAppModal.svelte | 7 +++-- .../src/components/start/Steps/User.svelte | 14 ++++----- packages/builder/src/constants/index.js | 2 +- packages/client/src/api/auth.js | 8 ++--- packages/client/src/store/auth.js | 4 +-- packages/server/src/api/controllers/auth.js | 10 +++---- packages/server/src/api/controllers/user.js | 30 ++++++++----------- .../src/api/routes/tests/couchTestUtils.js | 13 ++++---- .../server/src/api/routes/tests/user.spec.js | 12 ++++---- packages/server/src/api/routes/user.js | 4 +-- packages/server/src/app.js | 13 ++++++++ .../src/automations/steps/createUser.js | 13 ++++---- packages/server/src/constants/index.js | 9 +++--- packages/server/src/db/utils.js | 4 +-- packages/standard-components/components.json | 2 +- packages/standard-components/src/Login.svelte | 16 +++++----- 18 files changed, 92 insertions(+), 79 deletions(-) diff --git a/packages/builder/cypress/support/commands.js b/packages/builder/cypress/support/commands.js index 76f3417eac..0c177e6177 100644 --- a/packages/builder/cypress/support/commands.js +++ b/packages/builder/cypress/support/commands.js @@ -44,9 +44,9 @@ Cypress.Commands.add("createApp", name => { cy.contains("Next").click() - cy.get("input[name=username]") + cy.get("input[name=email]") .click() - .type("test") + .type("test@test.com") cy.get("input[name=password]") .click() .type("test") @@ -111,7 +111,7 @@ Cypress.Commands.add("addRow", values => { }) }) -Cypress.Commands.add("createUser", (username, password, accessLevel) => { +Cypress.Commands.add("createUser", (email, password, accessLevel) => { // Create User cy.contains("Users").click() @@ -123,7 +123,7 @@ Cypress.Commands.add("createUser", (username, password, accessLevel) => { .type(password) cy.get("input") .eq(1) - .type(username) + .type(email) cy.get("select") .first() .select(accessLevel) diff --git a/packages/builder/src/components/automation/SetupPanel/AutomationBlockSetup.svelte b/packages/builder/src/components/automation/SetupPanel/AutomationBlockSetup.svelte index 8bb4dc36dd..7e26b2155e 100644 --- a/packages/builder/src/components/automation/SetupPanel/AutomationBlockSetup.svelte +++ b/packages/builder/src/components/automation/SetupPanel/AutomationBlockSetup.svelte @@ -62,6 +62,8 @@ {:else if value.customType === 'password'} + {:else if value.customType === 'email'} + {:else if value.customType === 'table'} {:else if value.customType === 'row'} diff --git a/packages/builder/src/components/start/CreateAppModal.svelte b/packages/builder/src/components/start/CreateAppModal.svelte index 049b21c995..2eb644cdfb 100644 --- a/packages/builder/src/components/start/CreateAppModal.svelte +++ b/packages/builder/src/components/start/CreateAppModal.svelte @@ -52,7 +52,9 @@ applicationName: string().required("Your application must have a name."), }, { - username: string().required("Your application needs a first user."), + email: string() + .email() + .required("Your application needs a first user."), password: string().required( "Please enter a password for your first user." ), @@ -164,8 +166,7 @@ // Create user const user = { - name: $createAppStore.values.username, - username: $createAppStore.values.username, + email: $createAppStore.values.email, password: $createAppStore.values.password, accessLevelId: $createAppStore.values.accessLevelId, } diff --git a/packages/builder/src/components/start/Steps/User.svelte b/packages/builder/src/components/start/Steps/User.svelte index edc1fdf40b..e778d5f8e7 100644 --- a/packages/builder/src/components/start/Steps/User.svelte +++ b/packages/builder/src/components/start/Steps/User.svelte @@ -2,18 +2,18 @@ import { Input, Select } from "@budibase/bbui" export let validationErrors - let blurred = { username: false, password: false } + let blurred = { email: false, password: false }

Create your first User

(blurred.username = true)} - label="Username" - name="username" - placeholder="Username" - type="name" - error={blurred.username && validationErrors.username} /> + on:input={() => (blurred.email = true)} + label="Email" + name="email" + placeholder="Email" + type="email" + error={blurred.email && validationErrors.email} /> (blurred.password = true)} label="Password" diff --git a/packages/builder/src/constants/index.js b/packages/builder/src/constants/index.js index ae88d81bde..ff424bf0ef 100644 --- a/packages/builder/src/constants/index.js +++ b/packages/builder/src/constants/index.js @@ -3,7 +3,7 @@ export const TableNames = { } // fields on the user table that cannot be edited -export const UNEDITABLE_USER_FIELDS = ["username", "password", "accessLevelId"] +export const UNEDITABLE_USER_FIELDS = ["email", "password", "accessLevelId"] export const DEFAULT_PAGES_OBJECT = { main: { diff --git a/packages/client/src/api/auth.js b/packages/client/src/api/auth.js index 9273a00cc7..ddaaa7a491 100644 --- a/packages/client/src/api/auth.js +++ b/packages/client/src/api/auth.js @@ -3,15 +3,15 @@ import API from "./api" /** * Performs a log in request. */ -export const logIn = async ({ username, password }) => { - if (!username) { - return API.error("Please enter your username") +export const logIn = async ({ email, password }) => { + if (!email) { + return API.error("Please enter your email") } if (!password) { return API.error("Please enter your password") } return await API.post({ url: "/api/authenticate", - body: { username, password }, + body: { email, password }, }) } diff --git a/packages/client/src/store/auth.js b/packages/client/src/store/auth.js index a7c91a0972..d641113b4a 100644 --- a/packages/client/src/store/auth.js +++ b/packages/client/src/store/auth.js @@ -5,8 +5,8 @@ import { writable } from "svelte/store" const createAuthStore = () => { const store = writable("") - const logIn = async ({ username, password }) => { - const user = await API.logIn({ username, password }) + const logIn = async ({ email, password }) => { + const user = await API.logIn({ email, password }) if (!user.error) { store.set(user.token) location.reload() diff --git a/packages/server/src/api/controllers/auth.js b/packages/server/src/api/controllers/auth.js index 21136b0214..bac4ff6c2d 100644 --- a/packages/server/src/api/controllers/auth.js +++ b/packages/server/src/api/controllers/auth.js @@ -10,21 +10,21 @@ exports.authenticate = async ctx => { const appId = ctx.appId if (!appId) ctx.throw(400, "No appId") - const { username, password } = ctx.request.body + const { email, password } = ctx.request.body - if (!username) ctx.throw(400, "Username Required.") + if (!email) ctx.throw(400, "Email Required.") if (!password) ctx.throw(400, "Password Required.") - // Check the user exists in the instance DB by username + // Check the user exists in the instance DB by email const db = new CouchDB(appId) const app = await db.get(appId) let dbUser try { - dbUser = await db.get(generateUserID(username)) + dbUser = await db.get(generateUserID(email)) } catch (_) { // do not want to throw a 404 - as this could be - // used to determine valid usernames + // used to determine valid emails ctx.throw(401, "Invalid Credentials") } diff --git a/packages/server/src/api/controllers/user.js b/packages/server/src/api/controllers/user.js index f67ae72a89..83408d80f9 100644 --- a/packages/server/src/api/controllers/user.js +++ b/packages/server/src/api/controllers/user.js @@ -20,16 +20,10 @@ exports.fetch = async function(ctx) { exports.create = async function(ctx) { const db = new CouchDB(ctx.user.appId) - const { - username, - password, - name, - accessLevelId, - permissions, - } = ctx.request.body + const { email, password, name, accessLevelId, permissions } = ctx.request.body - if (!username || !password) { - ctx.throw(400, "Username and Password Required.") + if (!email || !password) { + ctx.throw(400, "email and Password Required.") } const accessLevel = await checkAccessLevel(db, accessLevelId) @@ -37,10 +31,10 @@ exports.create = async function(ctx) { if (!accessLevel) ctx.throw(400, "Invalid Access Level") const user = { - _id: generateUserID(username), - username, + _id: generateUserID(email), + email, password: await bcrypt.hash(password), - name: name || username, + name, type: "user", accessLevelId, permissions: permissions || [BUILTIN_PERMISSION_NAMES.POWER], @@ -54,7 +48,7 @@ exports.create = async function(ctx) { ctx.userId = response._id ctx.body = { _rev: response.rev, - username, + email, name, } } catch (err) { @@ -76,22 +70,22 @@ exports.update = async function(ctx) { user._rev = response.rev ctx.status = 200 - ctx.message = `User ${ctx.request.body.username} updated successfully.` + ctx.message = `User ${ctx.request.body.email} updated successfully.` ctx.body = response } exports.destroy = async function(ctx) { const database = new CouchDB(ctx.user.appId) - await database.destroy(generateUserID(ctx.params.username)) - ctx.message = `User ${ctx.params.username} deleted.` + await database.destroy(generateUserID(ctx.params.email)) + ctx.message = `User ${ctx.params.email} deleted.` ctx.status = 200 } exports.find = async function(ctx) { const database = new CouchDB(ctx.user.appId) - const user = await database.get(generateUserID(ctx.params.username)) + const user = await database.get(generateUserID(ctx.params.email)) ctx.body = { - username: user.username, + email: user.email, name: user.name, _rev: user._rev, } diff --git a/packages/server/src/api/routes/tests/couchTestUtils.js b/packages/server/src/api/routes/tests/couchTestUtils.js index ee12c1c6d8..783eca8920 100644 --- a/packages/server/src/api/routes/tests/couchTestUtils.js +++ b/packages/server/src/api/routes/tests/couchTestUtils.js @@ -118,7 +118,7 @@ exports.clearApplications = async request => { exports.createUser = async ( request, appId, - username = "babs", + email = "babs", password = "babs_password" ) => { const res = await request @@ -126,7 +126,7 @@ exports.createUser = async ( .set(exports.defaultHeaders(appId)) .send({ name: "Bill", - username, + email, password, accessLevelId: BUILTIN_LEVEL_IDS.POWER, }) @@ -174,15 +174,14 @@ const createUserWithPermissions = async ( request, appId, permissions, - username + email ) => { - const password = `password_${username}` + const password = `password_${email}` await request .post(`/api/users`) .set(exports.defaultHeaders(appId)) .send({ - name: username, - username, + email, password, accessLevelId: BUILTIN_LEVEL_IDS.POWER, permissions, @@ -203,7 +202,7 @@ const createUserWithPermissions = async ( Cookie: `budibase:${appId}:local=${anonToken}`, "x-budibase-app-id": appId, }) - .send({ username, password }) + .send({ email, password }) // returning necessary request headers return { diff --git a/packages/server/src/api/routes/tests/user.spec.js b/packages/server/src/api/routes/tests/user.spec.js index f9277039ea..94dcb6c63c 100644 --- a/packages/server/src/api/routes/tests/user.spec.js +++ b/packages/server/src/api/routes/tests/user.spec.js @@ -34,8 +34,8 @@ describe("/users", () => { describe("fetch", () => { it("returns a list of users from an instance db", async () => { - await createUser(request, appId, "brenda", "brendas_password") - await createUser(request, appId, "pam", "pam_password") + await createUser(request, appId, "brenda@brenda.com", "brendas_password") + await createUser(request, appId, "pam@pam.com", "pam_password") const res = await request .get(`/api/users`) .set(defaultHeaders(appId)) @@ -43,8 +43,8 @@ describe("/users", () => { .expect(200) expect(res.body.length).toBe(2) - expect(res.body.find(u => u.username === "brenda")).toBeDefined() - expect(res.body.find(u => u.username === "pam")).toBeDefined() + expect(res.body.find(u => u.email === "brenda@brenda.com")).toBeDefined() + expect(res.body.find(u => u.email === "pam@pam.com")).toBeDefined() }) it("should apply authorization to endpoint", async () => { @@ -67,7 +67,7 @@ describe("/users", () => { const res = await request .post(`/api/users`) .set(defaultHeaders(appId)) - .send({ name: "Bill", username: "bill", password: "bills_password", accessLevelId: BUILTIN_LEVEL_IDS.POWER }) + .send({ name: "Bill", email: "bill@bill.com", password: "bills_password", accessLevelId: BUILTIN_LEVEL_IDS.POWER }) .expect(200) .expect('Content-Type', /json/) @@ -79,7 +79,7 @@ describe("/users", () => { await testPermissionsForEndpoint({ request, method: "POST", - body: { name: "brandNewUser", username: "brandNewUser", password: "yeeooo", accessLevelId: BUILTIN_LEVEL_IDS.POWER }, + body: { name: "brandNewUser", email: "brandNewUser@user.com", password: "yeeooo", accessLevelId: BUILTIN_LEVEL_IDS.POWER }, url: `/api/users`, appId: appId, permName1: BUILTIN_PERMISSION_NAMES.ADMIN, diff --git a/packages/server/src/api/routes/user.js b/packages/server/src/api/routes/user.js index 9394d842bd..1ad1d2363e 100644 --- a/packages/server/src/api/routes/user.js +++ b/packages/server/src/api/routes/user.js @@ -16,7 +16,7 @@ router controller.fetch ) .get( - "/api/users/:username", + "/api/users/:email", authorized(PermissionTypes.USER, PermissionLevels.READ), controller.find ) @@ -32,7 +32,7 @@ router controller.create ) .delete( - "/api/users/:username", + "/api/users/:email", authorized(PermissionTypes.USER, PermissionLevels.WRITE), usage, controller.destroy diff --git a/packages/server/src/app.js b/packages/server/src/app.js index 10eec1e66f..58526cdf4c 100644 --- a/packages/server/src/app.js +++ b/packages/server/src/app.js @@ -1,4 +1,5 @@ const Koa = require("koa") +const destroyable = require("server-destroy") const electron = require("electron") const koaBody = require("koa-body") const logger = require("koa-pino-logger") @@ -44,6 +45,7 @@ if (electron.app && electron.app.isPackaged) { } const server = http.createServer(app.callback()) +destroyable(server) server.on("close", () => console.log("Server Closed")) @@ -51,3 +53,14 @@ module.exports = server.listen(env.PORT || 4001, () => { console.log(`Budibase running on ${JSON.stringify(server.address())}`) automations.init() }) + +process.on("uncaughtException", err => { + console.error(err) + server.close() + server.destroy() +}) + +process.on("SIGTERM", () => { + server.close() + server.destroy() +}) diff --git a/packages/server/src/automations/steps/createUser.js b/packages/server/src/automations/steps/createUser.js index b24c2cbe56..2d5dc2a4fe 100644 --- a/packages/server/src/automations/steps/createUser.js +++ b/packages/server/src/automations/steps/createUser.js @@ -5,7 +5,7 @@ const usage = require("../../utilities/usageQuota") module.exports.definition = { description: "Create a new user", - tagline: "Create user {{inputs.username}}", + tagline: "Create user {{inputs.email}}", icon: "ri-user-add-line", name: "Create User", type: "ACTION", @@ -16,9 +16,10 @@ module.exports.definition = { schema: { inputs: { properties: { - username: { + email: { type: "string", - title: "Username", + customType: "email", + title: "Email", }, password: { type: "string", @@ -32,7 +33,7 @@ module.exports.definition = { pretty: accessLevels.BUILTIN_LEVEL_NAME_ARRAY, }, }, - required: ["username", "password", "accessLevelId"], + required: ["email", "password", "accessLevelId"], }, outputs: { properties: { @@ -59,13 +60,13 @@ module.exports.definition = { } module.exports.run = async function({ inputs, appId, apiKey }) { - const { username, password, accessLevelId } = inputs + const { email, password, accessLevelId } = inputs const ctx = { user: { appId: appId, }, request: { - body: { username, password, accessLevelId }, + body: { email, password, accessLevelId }, }, } diff --git a/packages/server/src/constants/index.js b/packages/server/src/constants/index.js index 24fe45eb6b..a1f70c6c62 100644 --- a/packages/server/src/constants/index.js +++ b/packages/server/src/constants/index.js @@ -12,17 +12,18 @@ const USERS_TABLE_SCHEMA = { views: {}, name: "Users", schema: { - username: { + email: { type: "string", constraints: { type: "string", + email: true, length: { maximum: "", }, presence: true, }, - fieldName: "username", - name: "username", + fieldName: "email", + name: "email", }, accessLevelId: { fieldName: "accessLevelId", @@ -35,7 +36,7 @@ const USERS_TABLE_SCHEMA = { }, }, }, - primaryDisplay: "username", + primaryDisplay: "email", } exports.AuthTypes = AuthTypes diff --git a/packages/server/src/db/utils.js b/packages/server/src/db/utils.js index 96fd92218e..1f347fe7af 100644 --- a/packages/server/src/db/utils.js +++ b/packages/server/src/db/utils.js @@ -101,10 +101,10 @@ exports.generateRowID = tableId => { /** * Gets parameters for retrieving users, this is a utility function for the getDocParams function. */ -exports.getUserParams = (username = "", otherProps = {}) => { +exports.getUserParams = (email = "", otherProps = {}) => { return getDocParams( DocumentTypes.ROW, - `${ViewNames.USERS}${SEPARATOR}${DocumentTypes.USER}${SEPARATOR}${username}`, + `${ViewNames.USERS}${SEPARATOR}${DocumentTypes.USER}${SEPARATOR}${email}`, otherProps ) } diff --git a/packages/standard-components/components.json b/packages/standard-components/components.json index 7504c15b40..c55c04f119 100644 --- a/packages/standard-components/components.json +++ b/packages/standard-components/components.json @@ -56,7 +56,7 @@ }, "login": { "name": "Login Control", - "description": "A control that accepts username, password an also handles password resets", + "description": "A control that accepts email, password an also handles password resets", "props": { "logo": "string", "title": "string", diff --git a/packages/standard-components/src/Login.svelte b/packages/standard-components/src/Login.svelte index 4193f0a684..27c540abc7 100644 --- a/packages/standard-components/src/Login.svelte +++ b/packages/standard-components/src/Login.svelte @@ -10,7 +10,7 @@ export let buttonClass = "" export let inputClass = "" - let username = "" + let email = "" let password = "" let loading = false let error = false @@ -24,7 +24,7 @@ const login = async () => { loading = true - await authStore.actions.logIn({ username, password }) + await authStore.actions.logIn({ email, password }) loading = false } @@ -32,7 +32,9 @@
{#if logo} -
logo
+
+ logo +
{/if} {#if title} @@ -42,9 +44,9 @@
@@ -62,7 +64,7 @@
{#if error} -
Incorrect username or password
+
Incorrect email or password
{/if}
From ad4e4e46793a7abd8f026b7fa740031bf1318dad Mon Sep 17 00:00:00 2001 From: Martin McKeaveney Date: Fri, 4 Dec 2020 13:28:19 +0000 Subject: [PATCH 2/5] update user id generation --- packages/server/src/db/utils.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/server/src/db/utils.js b/packages/server/src/db/utils.js index 1f347fe7af..02ffd5019c 100644 --- a/packages/server/src/db/utils.js +++ b/packages/server/src/db/utils.js @@ -111,11 +111,11 @@ exports.getUserParams = (email = "", otherProps = {}) => { /** * Generates a new user ID based on the passed in username. - * @param {string} username The username which the ID is going to be built up of. + * @param {string} email The email which the ID is going to be built up of. * @returns {string} The new user ID which the user doc can be stored under. */ -exports.generateUserID = username => { - return `${DocumentTypes.ROW}${SEPARATOR}${ViewNames.USERS}${SEPARATOR}${DocumentTypes.USER}${SEPARATOR}${username}` +exports.generateUserID = email => { + return `${DocumentTypes.ROW}${SEPARATOR}${ViewNames.USERS}${SEPARATOR}${DocumentTypes.USER}${SEPARATOR}${email}` } /** From 15279f2c416b2c8eb1649870761ca3e292eb7610 Mon Sep 17 00:00:00 2001 From: Martin McKeaveney Date: Fri, 4 Dec 2020 14:46:21 +0000 Subject: [PATCH 3/5] lint --- packages/builder/cypress/integration/createUser.spec.js | 2 +- .../components/backend/DataTable/RowFieldControl.svelte | 9 ++++++++- packages/standard-components/src/Form.svelte | 9 ++++++++- packages/standard-components/src/Login.svelte | 4 +--- 4 files changed, 18 insertions(+), 6 deletions(-) diff --git a/packages/builder/cypress/integration/createUser.spec.js b/packages/builder/cypress/integration/createUser.spec.js index cbde6179b2..a5f9934dd7 100644 --- a/packages/builder/cypress/integration/createUser.spec.js +++ b/packages/builder/cypress/integration/createUser.spec.js @@ -9,7 +9,7 @@ context('Create a User', () => { // https://on.cypress.io/interacting-with-elements it('should create a user', () => { - cy.createUser("bbuser", "test", "ADMIN") + cy.createUser("bbuser@test.com", "test", "ADMIN") // // Check to make sure user was created! cy.contains("bbuser").should('be.visible') diff --git a/packages/builder/src/components/backend/DataTable/RowFieldControl.svelte b/packages/builder/src/components/backend/DataTable/RowFieldControl.svelte index 583a1d7ff5..8b1c2e18e6 100644 --- a/packages/builder/src/components/backend/DataTable/RowFieldControl.svelte +++ b/packages/builder/src/components/backend/DataTable/RowFieldControl.svelte @@ -1,5 +1,12 @@