diff --git a/packages/builder/src/pages/builder/admin/index.svelte b/packages/builder/src/pages/builder/admin/index.svelte
index 99731b8285..7ef2d6d290 100644
--- a/packages/builder/src/pages/builder/admin/index.svelte
+++ b/packages/builder/src/pages/builder/admin/index.svelte
@@ -4,37 +4,45 @@
Heading,
notifications,
Layout,
- Input,
Body,
- ActionButton,
Modal,
} from "@budibase/bbui"
import { goto } from "@roxi/routify"
import { API } from "api"
import { admin, auth } from "stores/portal"
- import PasswordRepeatInput from "components/common/users/PasswordRepeatInput.svelte"
import ImportAppsModal from "./_components/ImportAppsModal.svelte"
import Logo from "assets/bb-emblem.svg"
import { onMount } from "svelte"
+ import { FancyForm, FancyInput, ActionButton } from "@budibase/bbui"
+ import { TestimonialPage } from "@budibase/frontend-core/src/components"
+ import { handleError, passwordsMatch } from "../auth/_components/utils"
- let adminUser = {}
- let error
let modal
+ let form
+ let errors = {}
+ let formData = {}
+ let submitted = false
$: tenantId = $auth.tenantId
- $: multiTenancyEnabled = $admin.multiTenancy
$: cloud = $admin.cloud
$: imported = $admin.importComplete
+ $: multiTenancyEnabled = $admin.multiTenancy
async function save() {
+ form.validate()
+ if (Object.keys(errors).length > 0) {
+ return
+ }
+ submitted = true
try {
- adminUser.tenantId = tenantId
+ const adminUser = { ...formData, tenantId }
// Save the admin user
await API.createAdminUser(adminUser)
notifications.success("Admin user created")
await admin.init()
$goto("../portal")
} catch (error) {
+ submitted = false
notifications.error("Failed to create admin user")
}
}
@@ -53,35 +61,109 @@
-
-
-
+
+
+
+
-
- Create an admin user
-
- The admin user has access to everything in Budibase.
-
-
-
-
-
-
-
-
- Create super admin user
-
- {#if multiTenancyEnabled}
- {
- admin.unload()
- $goto("../auth/org")
- }}
- >
- Change organisation
-
- {:else if !cloud && !imported}
+ Create an admin user
+ The admin user has access to everything in Budibase.
+
+
+
+ {
+ formData = {
+ ...formData,
+ email: e.detail,
+ }
+ }}
+ validate={() => {
+ handleError(() => {
+ return {
+ email: !formData.email
+ ? "Please enter a valid email"
+ : undefined,
+ }
+ }, errors)
+ }}
+ disabled={submitted}
+ error={errors.email}
+ />
+ {
+ formData = {
+ ...formData,
+ password: e.detail,
+ }
+ }}
+ validate={() => {
+ handleError(() => {
+ let err = {}
+
+ err["password"] = !formData.password
+ ? "Please enter a password"
+ : undefined
+
+ err["confirmationPassword"] =
+ !passwordsMatch(
+ formData.password,
+ formData.confirmationPassword
+ ) && formData.confirmationPassword
+ ? "Passwords must match"
+ : undefined
+
+ return err
+ }, errors)
+ }}
+ error={errors.password}
+ disabled={submitted}
+ />
+ {
+ formData = {
+ ...formData,
+ confirmationPassword: e.detail,
+ }
+ }}
+ validate={() => {
+ handleError(() => {
+ return {
+ confirmationPassword:
+ !passwordsMatch(
+ formData.password,
+ formData.confirmationPassword
+ ) && formData.password
+ ? "Passwords must match"
+ : undefined,
+ }
+ }, errors)
+ }}
+ error={errors.confirmationPassword}
+ disabled={submitted}
+ />
+
+
+
+ 0 || submitted}
+ on:click={save}
+ >
+ Create super admin user
+
+
+
+
+ {#if !cloud && !imported && !multiTenancyEnabled}
{
@@ -91,28 +173,13 @@
Import from cloud
{/if}
-
+
-
-
+
+
diff --git a/packages/builder/src/pages/builder/auth/_components/GoogleButton.svelte b/packages/builder/src/pages/builder/auth/_components/GoogleButton.svelte
index 0acaa127cc..d8e1da7072 100644
--- a/packages/builder/src/pages/builder/auth/_components/GoogleButton.svelte
+++ b/packages/builder/src/pages/builder/auth/_components/GoogleButton.svelte
@@ -1,5 +1,5 @@
{#if show}
-
window.open(`/api/global/auth/${tenantId}/google`, "_blank")}
>
-
-
-
Sign in with Google
-
-
+ Log in with Google
+
{/if}
-
-
diff --git a/packages/builder/src/pages/builder/auth/_components/OIDCButton.svelte b/packages/builder/src/pages/builder/auth/_components/OIDCButton.svelte
index 27f5bde186..396bde3cb0 100644
--- a/packages/builder/src/pages/builder/auth/_components/OIDCButton.svelte
+++ b/packages/builder/src/pages/builder/auth/_components/OIDCButton.svelte
@@ -1,5 +1,5 @@
{#if show}
-
window.open(
`/api/global/auth/${$auth.tenantId}/oidc/configs/${$oidc.uuid}`,
"_blank"
)}
>
-
-
-
{`Sign in with ${$oidc.name || "OIDC"}`}
-
-
+ {`Log in with ${$oidc.name || "OIDC"}`}
+
{/if}
-
-
diff --git a/packages/builder/src/pages/builder/auth/_components/utils.js b/packages/builder/src/pages/builder/auth/_components/utils.js
new file mode 100644
index 0000000000..0ca42292b9
--- /dev/null
+++ b/packages/builder/src/pages/builder/auth/_components/utils.js
@@ -0,0 +1,18 @@
+exports.handleError = (validate, errors) => {
+ const err = validate()
+ let update = { ...errors, ...err }
+ errors = Object.keys(update).reduce((acc, key) => {
+ if (update[key]) {
+ acc[key] = update[key]
+ }
+ return acc
+ }, {})
+}
+
+exports.passwordsMatch = (password, confirmation) => {
+ let confirm = confirmation?.trim()
+ let pwd = password?.trim()
+ return (
+ typeof confirm === "string" && typeof pwd === "string" && confirm == pwd
+ )
+}
diff --git a/packages/builder/src/pages/builder/auth/forgot.svelte b/packages/builder/src/pages/builder/auth/forgot.svelte
index 7227fd6377..13c8331c2d 100644
--- a/packages/builder/src/pages/builder/auth/forgot.svelte
+++ b/packages/builder/src/pages/builder/auth/forgot.svelte
@@ -1,25 +1,35 @@
-
+
+
+
+ {
+ email = e.detail
+ }}
+ validate={() => {
+ if (!email) {
+ return "Please enter your email"
+ }
+ return null
+ }}
+ {error}
+ disabled={submitted}
+ />
+
+
+
+
+ Reset password
+
+
+
+
diff --git a/packages/builder/src/pages/builder/auth/login.svelte b/packages/builder/src/pages/builder/auth/login.svelte
index d8633a4fbc..b80dac44e7 100644
--- a/packages/builder/src/pages/builder/auth/login.svelte
+++ b/packages/builder/src/pages/builder/auth/login.svelte
@@ -5,7 +5,6 @@
Button,
Divider,
Heading,
- Input,
Layout,
notifications,
Link,
@@ -14,22 +13,30 @@
import { auth, organisation, oidc, admin } from "stores/portal"
import GoogleButton from "./_components/GoogleButton.svelte"
import OIDCButton from "./_components/OIDCButton.svelte"
+ import { handleError } from "./_components/utils"
import Logo from "assets/bb-emblem.svg"
+ import { TestimonialPage } from "@budibase/frontend-core/src/components"
+ import { FancyForm, FancyInput } from "@budibase/bbui"
import { onMount } from "svelte"
- let username = ""
- let password = ""
let loaded = false
+ let form
+ let errors = {}
+ let formData = {}
$: company = $organisation.company || "Budibase"
- $: multiTenancyEnabled = $admin.multiTenancy
$: cloud = $admin.cloud
async function login() {
+ form.validate()
+ if (Object.keys(errors).length > 0) {
+ console.log("errors")
+ return
+ }
try {
await auth.login({
- username: username.trim(),
- password,
+ username: formData?.username.trim(),
+ password: formData?.password,
})
if ($auth?.user?.forceResetPassword) {
$goto("./reset")
@@ -57,75 +64,98 @@
-
+
+ {#if loaded && ($organisation.google || $organisation.oidc)}
+
+
+
+
+
+ {/if}
+
+ {
+ formData = {
+ ...formData,
+ username: e.detail,
+ }
+ }}
+ validate={() => {
+ handleError(() => {
+ return {
+ username: !formData.username
+ ? "Please enter a valid email"
+ : undefined,
+ }
+ }, errors)
+ }}
+ error={errors.username}
+ />
+ {
+ formData = {
+ ...formData,
+ password: e.detail,
+ }
+ }}
+ validate={() => {
+ handleError(() => {
+ return {
+ password: !formData.password
+ ? "Please enter your password"
+ : undefined,
+ }
+ }, errors)
+ }}
+ error={errors.password}
+ />
+
+
+
+ 0} on:click={login}>
+ Log in to {company}
+
+
+
+
+
$goto("./forgot")}>
+ Forgot password
+
+
+
+
+ {#if cloud}
+
+ By using Budibase Cloud
+
+ you are agreeing to our
+
+ License Agreement
+
+
+ {/if}
+
+
diff --git a/packages/builder/src/stores/portal/users.js b/packages/builder/src/stores/portal/users.js
index e9bdf790f2..1510207604 100644
--- a/packages/builder/src/stores/portal/users.js
+++ b/packages/builder/src/stores/portal/users.js
@@ -29,13 +29,19 @@ export function createUsersStore() {
async function invite(payload) {
return API.inviteUsers(payload)
}
- async function acceptInvite(inviteCode, password) {
+ async function acceptInvite(inviteCode, password, firstName, lastName) {
return API.acceptInvite({
inviteCode,
password,
+ firstName,
+ lastName,
})
}
+ async function fetchInvite(inviteCode) {
+ return API.getUserInvite(inviteCode)
+ }
+
async function create(data) {
let mappedUsers = data.users.map(user => {
const body = {
@@ -101,6 +107,7 @@ export function createUsersStore() {
fetch,
invite,
acceptInvite,
+ fetchInvite,
create,
save,
bulkDelete,
diff --git a/packages/frontend-core/src/api/user.js b/packages/frontend-core/src/api/user.js
index 5c4f070802..9875605ce0 100644
--- a/packages/frontend-core/src/api/user.js
+++ b/packages/frontend-core/src/api/user.js
@@ -146,6 +146,16 @@ export const buildUserEndpoints = API => ({
})
},
+ /**
+ * Retrieves the invitation associated with a provided code.
+ * @param code The unique code for the target invite
+ */
+ getUserInvite: async code => {
+ return await API.get({
+ url: `/api/global/users/invite/${code}`,
+ })
+ },
+
/**
* Invites multiple users to the current tenant.
* @param users An array of users to invite
@@ -168,13 +178,17 @@ export const buildUserEndpoints = API => ({
* Accepts an invite to join the platform and creates a user.
* @param inviteCode the invite code sent in the email
* @param password the password for the newly created user
+ * @param firstName the first name of the new user
+ * @param lastName the last name of the new user
*/
- acceptInvite: async ({ inviteCode, password }) => {
+ acceptInvite: async ({ inviteCode, password, firstName, lastName }) => {
return await API.post({
url: "/api/global/users/invite/accept",
body: {
inviteCode,
password,
+ firstName,
+ lastName,
},
})
},
diff --git a/packages/worker/src/api/controllers/global/users.ts b/packages/worker/src/api/controllers/global/users.ts
index 10741f3725..817480151d 100644
--- a/packages/worker/src/api/controllers/global/users.ts
+++ b/packages/worker/src/api/controllers/global/users.ts
@@ -210,6 +210,19 @@ export const inviteMultiple = async (ctx: any) => {
ctx.body = await sdk.users.invite(request)
}
+export const checkInvite = async (ctx: any) => {
+ const { code } = ctx.params
+ let invite
+ try {
+ invite = await checkInviteCode(code, false)
+ } catch (e) {
+ ctx.throw(400, "There was a problem with the invite")
+ }
+ ctx.body = {
+ email: invite.email,
+ }
+}
+
export const inviteAccept = async (ctx: any) => {
const { inviteCode, password, firstName, lastName } = ctx.request.body
try {
diff --git a/packages/worker/src/api/index.ts b/packages/worker/src/api/index.ts
index e37c6c2d94..d8df62f532 100644
--- a/packages/worker/src/api/index.ts
+++ b/packages/worker/src/api/index.ts
@@ -62,6 +62,10 @@ const PUBLIC_ENDPOINTS = [
route: "/api/system/restored",
method: "POST",
},
+ {
+ route: "/api/global/users/invite",
+ method: "GET",
+ },
]
const NO_TENANCY_ENDPOINTS = [
diff --git a/packages/worker/src/api/routes/global/users.ts b/packages/worker/src/api/routes/global/users.ts
index 26cba8e1ab..a73462b235 100644
--- a/packages/worker/src/api/routes/global/users.ts
+++ b/packages/worker/src/api/routes/global/users.ts
@@ -38,6 +38,13 @@ function buildInviteMultipleValidation() {
))
}
+function buildInviteLookupValidation() {
+ // prettier-ignore
+ return auth.joiValidator.params(Joi.object({
+ code: Joi.string().required()
+ }).unknown(true))
+}
+
const createUserAdminOnly = (ctx: any, next: any) => {
if (!ctx.request.body._id) {
return auth.adminOnly(ctx, next)
@@ -51,6 +58,8 @@ function buildInviteAcceptValidation() {
return auth.joiValidator.body(Joi.object({
inviteCode: Joi.string().required(),
password: Joi.string().required(),
+ firstName: Joi.string().required(),
+ lastName: Joi.string().optional(),
}).required().unknown(true))
}
@@ -91,6 +100,11 @@ router
)
// non-global endpoints
+ .get(
+ "/api/global/users/invite/:code",
+ buildInviteLookupValidation(),
+ controller.checkInvite
+ )
.post(
"/api/global/users/invite/accept",
buildInviteAcceptValidation(),