Merge branch 'user-app-list' of github.com:Budibase/budibase into user-app-list
This commit is contained in:
commit
db3497c83a
|
@ -22,6 +22,7 @@
|
||||||
.container {
|
.container {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
|
position: relative;
|
||||||
}
|
}
|
||||||
.paddingX-S {
|
.paddingX-S {
|
||||||
padding-left: var(--spacing-s);
|
padding-left: var(--spacing-s);
|
||||||
|
|
|
@ -3,27 +3,17 @@
|
||||||
|
|
||||||
export let size = "M"
|
export let size = "M"
|
||||||
export let serif = false
|
export let serif = false
|
||||||
export let noPadding = false
|
export let weight = null
|
||||||
export let weight = 400
|
export let textAlign = null
|
||||||
export let textAlign = "left"
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<p
|
<p
|
||||||
style="font-weight:{weight};text-align:{textAlign};"
|
style={`
|
||||||
class:noPadding
|
${weight ? `font-weight:${weight};` : ""}
|
||||||
|
${textAlign ? `text-align:${textAlign};` : ""}
|
||||||
|
`}
|
||||||
class="spectrum-Body spectrum-Body--size{size}"
|
class="spectrum-Body spectrum-Body--size{size}"
|
||||||
class:spectrum-Body--serif={serif}
|
class:spectrum-Body--serif={serif}
|
||||||
>
|
>
|
||||||
<slot />
|
<slot />
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<style>
|
|
||||||
p {
|
|
||||||
margin-top: 0.75em;
|
|
||||||
margin-bottom: 0.75em;
|
|
||||||
}
|
|
||||||
.noPadding {
|
|
||||||
padding: 0;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
|
@ -3,12 +3,14 @@
|
||||||
|
|
||||||
// Sizes
|
// Sizes
|
||||||
export let size = "M"
|
export let size = "M"
|
||||||
export let textAlign = "left"
|
export let textAlign
|
||||||
export let noPadding = false
|
export let noPadding = false
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<h1 style="text-align:{textAlign};"
|
<h1
|
||||||
class:noPadding
|
style="{textAlign ? `text-align:${textAlign}` : ``}"
|
||||||
class="spectrum-Heading spectrum-Heading--size{size}">
|
class:noPadding
|
||||||
|
class="spectrum-Heading spectrum-Heading--size{size}"
|
||||||
|
>
|
||||||
<slot />
|
<slot />
|
||||||
</h1>
|
</h1>
|
||||||
|
|
|
@ -47,6 +47,8 @@
|
||||||
})
|
})
|
||||||
schema.email.displayName = "Email"
|
schema.email.displayName = "Email"
|
||||||
schema.roleId.displayName = "Role"
|
schema.roleId.displayName = "Role"
|
||||||
|
schema.firstName.displayName = "First Name"
|
||||||
|
schema.lastName.displayName = "Last Name"
|
||||||
if (schema.status) {
|
if (schema.status) {
|
||||||
schema.status.displayName = "Status"
|
schema.status.displayName = "Status"
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,6 +32,8 @@
|
||||||
delete customSchema["email"]
|
delete customSchema["email"]
|
||||||
delete customSchema["roleId"]
|
delete customSchema["roleId"]
|
||||||
delete customSchema["status"]
|
delete customSchema["status"]
|
||||||
|
delete customSchema["firstName"]
|
||||||
|
delete customSchema["lastName"]
|
||||||
return Object.entries(customSchema)
|
return Object.entries(customSchema)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -87,6 +89,14 @@
|
||||||
meta={{ name: "password", type: "password" }}
|
meta={{ name: "password", type: "password" }}
|
||||||
bind:value={row.password}
|
bind:value={row.password}
|
||||||
/>
|
/>
|
||||||
|
<RowFieldControl
|
||||||
|
meta={{ ...tableSchema.firstName, name: "First Name" }}
|
||||||
|
bind:value={row.firstName}
|
||||||
|
/>
|
||||||
|
<RowFieldControl
|
||||||
|
meta={{ ...tableSchema.lastName, name: "Last Name" }}
|
||||||
|
bind:value={row.lastName}
|
||||||
|
/>
|
||||||
<!-- Defer rendering this select until roles load, otherwise the initial
|
<!-- Defer rendering this select until roles load, otherwise the initial
|
||||||
selection is always undefined -->
|
selection is always undefined -->
|
||||||
<Select
|
<Select
|
||||||
|
|
|
@ -0,0 +1,43 @@
|
||||||
|
<script>
|
||||||
|
import { Layout, Input } from "@budibase/bbui"
|
||||||
|
import {
|
||||||
|
createValidationStore,
|
||||||
|
requiredValidator,
|
||||||
|
} from "../../../helpers/validation"
|
||||||
|
|
||||||
|
export let password
|
||||||
|
export let error
|
||||||
|
|
||||||
|
const [firstPassword, passwordError, firstTouched] = createValidationStore(
|
||||||
|
"",
|
||||||
|
requiredValidator
|
||||||
|
)
|
||||||
|
const [repeatPassword, _, repeatTouched] = createValidationStore(
|
||||||
|
"",
|
||||||
|
requiredValidator
|
||||||
|
)
|
||||||
|
|
||||||
|
$: password = $firstPassword
|
||||||
|
$: error =
|
||||||
|
!$firstPassword ||
|
||||||
|
!$firstTouched ||
|
||||||
|
!$repeatTouched ||
|
||||||
|
$firstPassword !== $repeatPassword
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Layout gap="XS" noPadding>
|
||||||
|
<Input
|
||||||
|
label="Password"
|
||||||
|
type="password"
|
||||||
|
error={$firstTouched && $passwordError}
|
||||||
|
bind:value={$firstPassword}
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
label="Repeat Password"
|
||||||
|
type="password"
|
||||||
|
error={$repeatTouched &&
|
||||||
|
$firstPassword !== $repeatPassword &&
|
||||||
|
"Passwords must match"}
|
||||||
|
bind:value={$repeatPassword}
|
||||||
|
/>
|
||||||
|
</Layout>
|
|
@ -1,11 +1,25 @@
|
||||||
<script>
|
<script>
|
||||||
import { Input, Button, Layout, Body, Heading } from "@budibase/bbui"
|
import {
|
||||||
|
notifications,
|
||||||
|
Input,
|
||||||
|
Button,
|
||||||
|
Layout,
|
||||||
|
Body,
|
||||||
|
Heading,
|
||||||
|
} from "@budibase/bbui"
|
||||||
import { organisation } from "stores/portal"
|
import { organisation } from "stores/portal"
|
||||||
|
import { auth } from "stores/backend"
|
||||||
|
|
||||||
let username = ""
|
let email = ""
|
||||||
let password = ""
|
|
||||||
|
|
||||||
async function reset() {}
|
async function forgot() {
|
||||||
|
try {
|
||||||
|
await auth.forgotPassword(email)
|
||||||
|
notifications.success("Email sent - please check your inbox")
|
||||||
|
} catch (err) {
|
||||||
|
notifications.error("Unable to send reset password link")
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="login">
|
<div class="login">
|
||||||
|
@ -20,9 +34,11 @@
|
||||||
No problem! Just enter your account's email address and we'll send you
|
No problem! Just enter your account's email address and we'll send you
|
||||||
a link to reset it.
|
a link to reset it.
|
||||||
</Body>
|
</Body>
|
||||||
<Input label="Email" bind:value={username} />
|
<Input label="Email" bind:value={email} />
|
||||||
</Layout>
|
</Layout>
|
||||||
<Button cta on:click={reset}>Reset your password</Button>
|
<Button cta on:click={forgot} disabled={!email}>
|
||||||
|
Reset your password
|
||||||
|
</Button>
|
||||||
</Layout>
|
</Layout>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,17 +1,16 @@
|
||||||
<script>
|
<script>
|
||||||
import { Link } from "@budibase/bbui"
|
import { ActionButton } from "@budibase/bbui"
|
||||||
import GoogleLogo from "/assets/google-logo.png"
|
import GoogleLogo from "/assets/google-logo.png"
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="outer">
|
<ActionButton>
|
||||||
<Link target="_blank" href="/api/admin/auth/google">
|
<a target="_blank" href="/api/admin/auth/google">
|
||||||
<div class="inner">
|
<div class="inner">
|
||||||
<img src={GoogleLogo} alt="google icon" />
|
<img src={GoogleLogo} alt="google icon" />
|
||||||
<p>Sign in with Google</p>
|
<p>Sign in with Google</p>
|
||||||
</div>
|
</div>
|
||||||
</Link>
|
</a>
|
||||||
</div>
|
</ActionButton>
|
||||||
|
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.outer {
|
.outer {
|
||||||
|
@ -35,10 +34,8 @@
|
||||||
.inner p {
|
.inner p {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
.outer :global(a) {
|
a {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
font-weight: 500;
|
color: inherit;
|
||||||
font-size: var(--font-size-m);
|
|
||||||
color: #fff;
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -40,8 +40,8 @@
|
||||||
<Heading>Sign in to Budibase</Heading>
|
<Heading>Sign in to Budibase</Heading>
|
||||||
</Layout>
|
</Layout>
|
||||||
<GoogleButton />
|
<GoogleButton />
|
||||||
|
<Divider noGrid />
|
||||||
<Layout gap="XS" noPadding>
|
<Layout gap="XS" noPadding>
|
||||||
<Divider noGrid />
|
|
||||||
<Body size="S" textAlign="center">Sign in with email</Body>
|
<Body size="S" textAlign="center">Sign in with email</Body>
|
||||||
<Input label="Email" bind:value={username} />
|
<Input label="Email" bind:value={username} />
|
||||||
<Input
|
<Input
|
||||||
|
@ -51,7 +51,7 @@
|
||||||
bind:value={password}
|
bind:value={password}
|
||||||
/>
|
/>
|
||||||
</Layout>
|
</Layout>
|
||||||
<Layout gap="S" noPadding>
|
<Layout gap="XS" noPadding>
|
||||||
<Button cta on:click={login}>Sign in to Budibase</Button>
|
<Button cta on:click={login}>Sign in to Budibase</Button>
|
||||||
<ActionButton quiet on:click={() => $goto("./forgot")}>
|
<ActionButton quiet on:click={() => $goto("./forgot")}>
|
||||||
Forgot password?
|
Forgot password?
|
||||||
|
|
|
@ -1,12 +1,23 @@
|
||||||
<script>
|
<script>
|
||||||
import { Input, Button, Layout, Body, Heading } from "@budibase/bbui"
|
import { notifications, Button, Layout, Body, Heading } from "@budibase/bbui"
|
||||||
import { params } from "@roxi/routify"
|
|
||||||
import { organisation } from "stores/portal"
|
import { organisation } from "stores/portal"
|
||||||
|
import PasswordRepeatInput from "components/common/users/PasswordRepeatInput.svelte"
|
||||||
|
import { params, goto } from "@roxi/routify"
|
||||||
|
import { auth } from "stores/backend"
|
||||||
|
|
||||||
const resetCode = $params["?code"]
|
const resetCode = $params["?code"]
|
||||||
let password = ""
|
let password, error
|
||||||
|
|
||||||
async function reset() {}
|
async function reset() {
|
||||||
|
try {
|
||||||
|
await auth.resetPassword(password, resetCode)
|
||||||
|
notifications.success("Password reset successfully")
|
||||||
|
// send them to login if reset successful
|
||||||
|
$goto("./login")
|
||||||
|
} catch (err) {
|
||||||
|
notifications.error("Unable to reset password")
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="login">
|
<div class="login">
|
||||||
|
@ -20,9 +31,11 @@
|
||||||
<Body size="S" textAlign="center">
|
<Body size="S" textAlign="center">
|
||||||
Please enter the new password you'd like to use.
|
Please enter the new password you'd like to use.
|
||||||
</Body>
|
</Body>
|
||||||
<Input label="Password" bind:value={password} />
|
<PasswordRepeatInput bind:password bind:error />
|
||||||
</Layout>
|
</Layout>
|
||||||
<Button cta on:click={reset}>Reset your password</Button>
|
<Button cta on:click={reset} disabled={error || !resetCode}>
|
||||||
|
Reset your password
|
||||||
|
</Button>
|
||||||
</Layout>
|
</Layout>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -48,7 +48,7 @@
|
||||||
</ActionMenu>
|
</ActionMenu>
|
||||||
</div>
|
</div>
|
||||||
<div class="status">
|
<div class="status">
|
||||||
<Body noPadding size="S">
|
<Body size="S">
|
||||||
Edited {Math.floor(1 + Math.random() * 10)} months ago
|
Edited {Math.floor(1 + Math.random() * 10)} months ago
|
||||||
</Body>
|
</Body>
|
||||||
{#if app.lockedBy}
|
{#if app.lockedBy}
|
||||||
|
|
|
@ -22,7 +22,13 @@
|
||||||
|
|
||||||
// Redirect to log in at any time if the user isn't authenticated
|
// Redirect to log in at any time if the user isn't authenticated
|
||||||
$: {
|
$: {
|
||||||
if (loaded && hasAdminUser && !$auth.user && !$isActive("./auth")) {
|
if (
|
||||||
|
loaded &&
|
||||||
|
hasAdminUser &&
|
||||||
|
!$auth.user &&
|
||||||
|
!$isActive("./auth") &&
|
||||||
|
!$isActive("./invite")
|
||||||
|
) {
|
||||||
$redirect("./auth/login")
|
$redirect("./auth/login")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,7 +36,7 @@
|
||||||
<img src={$organisation.logoUrl || "https://i.imgur.com/ZKyklgF.png"} />
|
<img src={$organisation.logoUrl || "https://i.imgur.com/ZKyklgF.png"} />
|
||||||
<Layout gap="XS" justifyItems="center" noPadding>
|
<Layout gap="XS" justifyItems="center" noPadding>
|
||||||
<Heading size="M">Create an admin user</Heading>
|
<Heading size="M">Create an admin user</Heading>
|
||||||
<Body size="M" textAlign="center" noPadding>
|
<Body size="M" textAlign="center">
|
||||||
The admin user has access to everything in Budibase.
|
The admin user has access to everything in Budibase.
|
||||||
</Body>
|
</Body>
|
||||||
</Layout>
|
</Layout>
|
||||||
|
|
|
@ -0,0 +1,40 @@
|
||||||
|
<script>
|
||||||
|
import { ModalContent, Body, Input, notifications } from "@budibase/bbui"
|
||||||
|
import { writable } from "svelte/store"
|
||||||
|
import { auth } from "stores/backend"
|
||||||
|
import { saveRow } from "components/backend/DataTable/api"
|
||||||
|
import { TableNames } from "constants"
|
||||||
|
|
||||||
|
const values = writable({
|
||||||
|
firstName: $auth.user.firstName,
|
||||||
|
lastName: $auth.user.lastName,
|
||||||
|
})
|
||||||
|
|
||||||
|
const updateInfo = async () => {
|
||||||
|
const newUser = {
|
||||||
|
...$auth.user,
|
||||||
|
firstName: $values.firstName,
|
||||||
|
lastName: $values.lastName,
|
||||||
|
}
|
||||||
|
console.log(newUser)
|
||||||
|
const response = await saveRow(newUser, TableNames.USERS)
|
||||||
|
if (response.ok) {
|
||||||
|
await auth.checkAuth()
|
||||||
|
notifications.success("Information updated successfully")
|
||||||
|
} else {
|
||||||
|
notifications.error("Failed to update information")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<ModalContent
|
||||||
|
title="Update user information"
|
||||||
|
confirmText="Update information"
|
||||||
|
onConfirm={updateInfo}
|
||||||
|
>
|
||||||
|
<Body size="S">
|
||||||
|
Personalise the platform by adding your first name and last name.
|
||||||
|
</Body>
|
||||||
|
<Input bind:value={$values.firstName} label="First name" />
|
||||||
|
<Input bind:value={$values.lastName} label="Last name" />
|
||||||
|
</ModalContent>
|
|
@ -10,6 +10,7 @@
|
||||||
Page,
|
Page,
|
||||||
Icon,
|
Icon,
|
||||||
Body,
|
Body,
|
||||||
|
Modal,
|
||||||
} from "@budibase/bbui"
|
} from "@budibase/bbui"
|
||||||
import { onMount } from "svelte"
|
import { onMount } from "svelte"
|
||||||
import { apps, organisation } from "stores/portal"
|
import { apps, organisation } from "stores/portal"
|
||||||
|
@ -17,8 +18,11 @@
|
||||||
import { goto } from "@roxi/routify"
|
import { goto } from "@roxi/routify"
|
||||||
import { AppStatus } from "constants"
|
import { AppStatus } from "constants"
|
||||||
import { gradient } from "actions"
|
import { gradient } from "actions"
|
||||||
|
import UpdateUserInfoModal from "./_components/UpdateUserInfoModal.svelte"
|
||||||
|
|
||||||
let loaded = false
|
let loaded = false
|
||||||
|
let userInfoModal
|
||||||
|
let userPasswordModal
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
await organisation.init()
|
await organisation.init()
|
||||||
|
@ -35,8 +39,10 @@
|
||||||
<img src={$organisation.logoUrl} />
|
<img src={$organisation.logoUrl} />
|
||||||
<div class="info-title">
|
<div class="info-title">
|
||||||
<Layout noPadding gap="XS">
|
<Layout noPadding gap="XS">
|
||||||
<Heading size="L">Hey {$auth.user.email}</Heading>
|
<Heading size="L">
|
||||||
<Body noPadding>
|
Hey {$auth.user.firstName || $auth.user.email}
|
||||||
|
</Heading>
|
||||||
|
<Body>
|
||||||
Welcome to the {$organisation.company} portal. Below you'll find
|
Welcome to the {$organisation.company} portal. Below you'll find
|
||||||
the list of apps that you have access to, as well as company news
|
the list of apps that you have access to, as well as company news
|
||||||
and the employee handbook.
|
and the employee handbook.
|
||||||
|
@ -47,7 +53,9 @@
|
||||||
<Avatar size="M" name="John Doe" />
|
<Avatar size="M" name="John Doe" />
|
||||||
<Icon size="XL" name="ChevronDown" />
|
<Icon size="XL" name="ChevronDown" />
|
||||||
</div>
|
</div>
|
||||||
<MenuItem icon="UserEdit">Update user information</MenuItem>
|
<MenuItem icon="UserEdit" on:click={() => userInfoModal.show()}>
|
||||||
|
Update user information
|
||||||
|
</MenuItem>
|
||||||
<MenuItem icon="LockClosed">Update password</MenuItem>
|
<MenuItem icon="LockClosed">Update password</MenuItem>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
icon="UserDeveloper"
|
icon="UserDeveloper"
|
||||||
|
@ -66,7 +74,7 @@
|
||||||
<div class="group">
|
<div class="group">
|
||||||
<Layout gap="S" noPadding>
|
<Layout gap="S" noPadding>
|
||||||
<div class="group-title">
|
<div class="group-title">
|
||||||
<Body weight="500" noPadding size="XS">GROUP</Body>
|
<Body weight="500" size="XS">GROUP</Body>
|
||||||
{#if $auth.user?.builder?.global}
|
{#if $auth.user?.builder?.global}
|
||||||
<Icon name="Settings" hoverable />
|
<Icon name="Settings" hoverable />
|
||||||
{/if}
|
{/if}
|
||||||
|
@ -76,7 +84,7 @@
|
||||||
<div class="preview" use:gradient={{ seed: app.name }} />
|
<div class="preview" use:gradient={{ seed: app.name }} />
|
||||||
<div class="app-info">
|
<div class="app-info">
|
||||||
<Heading size="XS">{app.name}</Heading>
|
<Heading size="XS">{app.name}</Heading>
|
||||||
<Body noPadding size="S">
|
<Body size="S">
|
||||||
Edited {Math.round(Math.random() * 10 + 1)} months ago
|
Edited {Math.round(Math.random() * 10 + 1)} months ago
|
||||||
</Body>
|
</Body>
|
||||||
</div>
|
</div>
|
||||||
|
@ -89,6 +97,9 @@
|
||||||
</div>
|
</div>
|
||||||
</Page>
|
</Page>
|
||||||
</div>
|
</div>
|
||||||
|
<Modal bind:this={userInfoModal}>
|
||||||
|
<UpdateUserInfoModal />
|
||||||
|
</Modal>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
@ -150,7 +161,7 @@
|
||||||
}
|
}
|
||||||
.preview {
|
.preview {
|
||||||
height: 40px;
|
height: 40px;
|
||||||
width: 40px;
|
width: 60px;
|
||||||
border-radius: var(--border-radius-s);
|
border-radius: var(--border-radius-s);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,29 +1,15 @@
|
||||||
<script>
|
<script>
|
||||||
import {
|
import { Layout, Heading, Body, Button, notifications } from "@budibase/bbui"
|
||||||
Layout,
|
|
||||||
Heading,
|
|
||||||
Body,
|
|
||||||
Input,
|
|
||||||
Button,
|
|
||||||
notifications,
|
|
||||||
} from "@budibase/bbui"
|
|
||||||
import { goto, params } from "@roxi/routify"
|
import { goto, params } from "@roxi/routify"
|
||||||
import { createValidationStore, requiredValidator } from "helpers/validation"
|
|
||||||
import { users, organisation } from "stores/portal"
|
import { users, organisation } from "stores/portal"
|
||||||
|
import PasswordRepeatInput from "components/common/users/PasswordRepeatInput.svelte"
|
||||||
|
|
||||||
const [password, passwordError, passwordTouched] = createValidationStore(
|
|
||||||
"",
|
|
||||||
requiredValidator
|
|
||||||
)
|
|
||||||
const [repeat, _, repeatTouched] = createValidationStore(
|
|
||||||
"",
|
|
||||||
requiredValidator
|
|
||||||
)
|
|
||||||
const inviteCode = $params["?code"]
|
const inviteCode = $params["?code"]
|
||||||
|
let password, error
|
||||||
|
|
||||||
async function acceptInvite() {
|
async function acceptInvite() {
|
||||||
try {
|
try {
|
||||||
const res = await users.acceptInvite(inviteCode, $password)
|
const res = await users.acceptInvite(inviteCode, password)
|
||||||
if (!res) {
|
if (!res) {
|
||||||
throw new Error(res.message)
|
throw new Error(res.message)
|
||||||
}
|
}
|
||||||
|
@ -41,31 +27,12 @@
|
||||||
<img src={$organisation.logoUrl || "https://i.imgur.com/ZKyklgF.png"} />
|
<img src={$organisation.logoUrl || "https://i.imgur.com/ZKyklgF.png"} />
|
||||||
<Layout gap="XS" justifyItems="center" noPadding>
|
<Layout gap="XS" justifyItems="center" noPadding>
|
||||||
<Heading size="M">Accept Invitation</Heading>
|
<Heading size="M">Accept Invitation</Heading>
|
||||||
<Body textAlign="center" size="M" noPadding>
|
<Body textAlign="center" size="M">
|
||||||
Please enter a password to set up your user.
|
Please enter a password to set up your user.
|
||||||
</Body>
|
</Body>
|
||||||
</Layout>
|
</Layout>
|
||||||
<Layout gap="XS" noPadding>
|
<PasswordRepeatInput bind:error bind:password />
|
||||||
<Input
|
<Button disabled={error} cta on:click={acceptInvite}>
|
||||||
label="Password"
|
|
||||||
type="password"
|
|
||||||
error={$passwordTouched && $passwordError}
|
|
||||||
bind:value={$password}
|
|
||||||
/>
|
|
||||||
<Input
|
|
||||||
label="Repeat Password"
|
|
||||||
type="password"
|
|
||||||
error={$repeatTouched &&
|
|
||||||
$password !== $repeat &&
|
|
||||||
"Passwords must match"}
|
|
||||||
bind:value={$repeat}
|
|
||||||
/>
|
|
||||||
</Layout>
|
|
||||||
<Button
|
|
||||||
disabled={!$passwordTouched || !$repeatTouched || $password !== $repeat}
|
|
||||||
cta
|
|
||||||
on:click={acceptInvite}
|
|
||||||
>
|
|
||||||
Accept invite
|
Accept invite
|
||||||
</Button>
|
</Button>
|
||||||
</Layout>
|
</Layout>
|
||||||
|
|
|
@ -59,44 +59,42 @@
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Page>
|
<Layout>
|
||||||
<Layout noPadding>
|
<Layout gap="XS" noPadding>
|
||||||
<div>
|
<Heading size="M">OAuth</Heading>
|
||||||
<Heading size="M">OAuth</Heading>
|
<Body>
|
||||||
<Body>
|
Every budibase app comes with basic authentication (email/password)
|
||||||
Every budibase app comes with basic authentication (email/password)
|
included. You can add additional authentication methods from the options
|
||||||
included. You can add additional authentication methods from the options
|
below.
|
||||||
below.
|
</Body>
|
||||||
</Body>
|
</Layout>
|
||||||
</div>
|
{#if google}
|
||||||
<Divider />
|
<Divider />
|
||||||
{#if google}
|
<Layout gap="XS" noPadding>
|
||||||
<div>
|
<Heading size="S">
|
||||||
<Heading size="S">
|
<span>
|
||||||
<span>
|
<GoogleLogo />
|
||||||
<GoogleLogo />
|
Google
|
||||||
Google
|
</span>
|
||||||
</span>
|
</Heading>
|
||||||
</Heading>
|
<Body size="S">
|
||||||
<Body>
|
To allow users to authenticate using their Google accounts, fill out the
|
||||||
To allow users to authenticate using their Google accounts, fill out
|
fields below.
|
||||||
the fields below.
|
</Body>
|
||||||
</Body>
|
</Layout>
|
||||||
</div>
|
<Layout gap="XS" noPadding>
|
||||||
|
|
||||||
{#each ConfigFields.Google as field}
|
{#each ConfigFields.Google as field}
|
||||||
<div class="form-row">
|
<div class="form-row">
|
||||||
<Label size="L">{field}</Label>
|
<Label size="L">{field}</Label>
|
||||||
<Input bind:value={google.config[field]} />
|
<Input bind:value={google.config[field]} />
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
<div>
|
</Layout>
|
||||||
<Button primary on:click={() => save(google)}>Save</Button>
|
<div>
|
||||||
</div>
|
<Button cta on:click={() => save(google)}>Save</Button>
|
||||||
<Divider />
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</Layout>
|
</Layout>
|
||||||
</Page>
|
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.form-row {
|
.form-row {
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
{#each bindings as binding}
|
{#each bindings as binding}
|
||||||
<MenuItem on:click={() => onBindingClick(binding)}>
|
<MenuItem on:click={() => onBindingClick(binding)}>
|
||||||
<Detail size="M">{binding.name}</Detail>
|
<Detail size="M">{binding.name}</Detail>
|
||||||
<Body size="XS" noPadding>{binding.description}</Body>
|
<Body size="XS">{binding.description}</Body>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
{/each}
|
{/each}
|
||||||
</Menu>
|
</Menu>
|
||||||
|
|
|
@ -102,59 +102,57 @@
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Page>
|
<Layout>
|
||||||
<header>
|
<Layout noPadding gap="XS">
|
||||||
<Heading size="M">Email</Heading>
|
<Heading size="M">Email</Heading>
|
||||||
<Body size="S">
|
<Body>
|
||||||
Sending email is not required, but highly recommended for processes such
|
Sending email is not required, but highly recommended for processes such
|
||||||
as password recovery. To setup automated auth emails, simply add the
|
as password recovery. To setup automated auth emails, simply add the
|
||||||
values below and click activate.
|
values below and click activate.
|
||||||
</Body>
|
</Body>
|
||||||
</header>
|
</Layout>
|
||||||
<Divider />
|
<Divider />
|
||||||
{#if smtpConfig}
|
{#if smtpConfig}
|
||||||
<div class="config-form">
|
<Layout gap="XS" noPadding>
|
||||||
<Heading size="S">SMTP</Heading>
|
<Heading size="S">SMTP</Heading>
|
||||||
<Body size="S">
|
<Body size="S">
|
||||||
To allow your app to benefit from automated auth emails, add your SMTP
|
To allow your app to benefit from automated auth emails, add your SMTP
|
||||||
details below.
|
details below.
|
||||||
</Body>
|
</Body>
|
||||||
<Layout gap="S">
|
</Layout>
|
||||||
<Heading size="S">
|
<Layout gap="XS" noPadding>
|
||||||
<span />
|
<div class="form-row">
|
||||||
</Heading>
|
<Label size="L">Host</Label>
|
||||||
<div class="form-row">
|
<Input bind:value={smtpConfig.config.host} />
|
||||||
<Label>Host</Label>
|
</div>
|
||||||
<Input bind:value={smtpConfig.config.host} />
|
<div class="form-row">
|
||||||
</div>
|
<Label size="L">Port</Label>
|
||||||
<div class="form-row">
|
<Input type="number" bind:value={smtpConfig.config.port} />
|
||||||
<Label>Port</Label>
|
</div>
|
||||||
<Input type="number" bind:value={smtpConfig.config.port} />
|
<div class="form-row">
|
||||||
</div>
|
<Label size="L">User</Label>
|
||||||
<div class="form-row">
|
<Input bind:value={smtpConfig.config.auth.user} />
|
||||||
<Label>User</Label>
|
</div>
|
||||||
<Input bind:value={smtpConfig.config.auth.user} />
|
<div class="form-row">
|
||||||
</div>
|
<Label size="L">Password</Label>
|
||||||
<div class="form-row">
|
<Input type="password" bind:value={smtpConfig.config.auth.pass} />
|
||||||
<Label>Password</Label>
|
</div>
|
||||||
<Input type="password" bind:value={smtpConfig.config.auth.pass} />
|
<div class="form-row">
|
||||||
</div>
|
<Label size="L">From email address</Label>
|
||||||
<div class="form-row">
|
<Input type="email" bind:value={smtpConfig.config.from} />
|
||||||
<Label>From email address</Label>
|
</div>
|
||||||
<Input type="email" bind:value={smtpConfig.config.from} />
|
</Layout>
|
||||||
</div>
|
<div>
|
||||||
</Layout>
|
|
||||||
<Button cta on:click={saveSmtp}>Save</Button>
|
<Button cta on:click={saveSmtp}>Save</Button>
|
||||||
</div>
|
</div>
|
||||||
<Divider />
|
<Divider />
|
||||||
|
<Layout gap="XS" noPadding>
|
||||||
<div class="config-form">
|
|
||||||
<Heading size="S">Templates</Heading>
|
<Heading size="S">Templates</Heading>
|
||||||
<Body size="S">
|
<Body size="S">
|
||||||
Budibase comes out of the box with ready-made email templates to help
|
Budibase comes out of the box with ready-made email templates to help
|
||||||
with user onboarding. Please refrain from changing the links.
|
with user onboarding. Please refrain from changing the links.
|
||||||
</Body>
|
</Body>
|
||||||
</div>
|
</Layout>
|
||||||
<Table
|
<Table
|
||||||
{customRenderers}
|
{customRenderers}
|
||||||
data={$email.templates}
|
data={$email.templates}
|
||||||
|
@ -165,27 +163,13 @@
|
||||||
allowEditColumns={false}
|
allowEditColumns={false}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
</Page>
|
</Layout>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.config-form {
|
|
||||||
margin-top: 42px;
|
|
||||||
margin-bottom: 42px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-row {
|
.form-row {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 20% 1fr;
|
grid-template-columns: 25% 1fr;
|
||||||
grid-gap: var(--spacing-l);
|
grid-gap: var(--spacing-l);
|
||||||
}
|
|
||||||
|
|
||||||
span {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: var(--spacing-s);
|
|
||||||
}
|
|
||||||
|
|
||||||
header {
|
|
||||||
margin-bottom: 42px;
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,43 +0,0 @@
|
||||||
<svg
|
|
||||||
width="18"
|
|
||||||
height="18"
|
|
||||||
viewBox="0 0 268 268"
|
|
||||||
fill="none"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
>
|
|
||||||
<g clip-path="url(#clip0)">
|
|
||||||
<path
|
|
||||||
d="M58.8037 109.043C64.0284 93.2355 74.1116 79.4822 87.615 69.7447C101.118
|
|
||||||
60.0073 117.352 54.783 134 54.8172C152.872 54.8172 169.934 61.5172 183.334
|
|
||||||
72.4828L222.328 33.5C198.566 12.7858 168.114 0 134 0C81.1817 0 35.711
|
|
||||||
30.1277 13.8467 74.2583L58.8037 109.043Z"
|
|
||||||
fill="#EA4335"
|
|
||||||
/>
|
|
||||||
<path
|
|
||||||
d="M179.113 201.145C166.942 208.995 151.487 213.183 134 213.183C117.418
|
|
||||||
213.217 101.246 208.034 87.7727 198.369C74.2993 188.703 64.2077 175.044
|
|
||||||
58.9265 159.326L13.8132 193.574C24.8821 215.978 42.012 234.828 63.2572
|
|
||||||
247.984C84.5024 261.14 109.011 268.075 134 268C166.752 268 198.041 256.353
|
|
||||||
221.48 234.5L179.125 201.145H179.113Z"
|
|
||||||
fill="#34A853"
|
|
||||||
/>
|
|
||||||
<path
|
|
||||||
d="M221.48 234.5C245.991 211.631 261.903 177.595 261.903 134C261.903
|
|
||||||
126.072 260.686 117.552 258.866 109.634H134V161.414H205.869C202.329
|
|
||||||
178.823 192.804 192.301 179.125 201.145L221.48 234.5Z"
|
|
||||||
fill="#4A90E2"
|
|
||||||
/>
|
|
||||||
<path
|
|
||||||
d="M58.9265 159.326C56.1947 151.162 54.8068 142.609 54.8172 134C54.8172
|
|
||||||
125.268 56.213 116.882 58.8037 109.043L13.8467 74.2584C4.64957 92.825
|
|
||||||
-0.0915078 113.28 1.86708e-05 134C1.86708e-05 155.44 4.96919 175.652
|
|
||||||
13.8132 193.574L58.9265 159.326Z"
|
|
||||||
fill="#FBBC05"
|
|
||||||
/>
|
|
||||||
</g>
|
|
||||||
<defs>
|
|
||||||
<clipPath id="clip0">
|
|
||||||
<rect width="268" height="268" fill="white" />
|
|
||||||
</clipPath>
|
|
||||||
</defs>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 1.5 KiB |
|
@ -1,115 +0,0 @@
|
||||||
<script>
|
|
||||||
import GoogleLogo from "./_logos/Google.svelte"
|
|
||||||
import {
|
|
||||||
Button,
|
|
||||||
Heading,
|
|
||||||
Divider,
|
|
||||||
Label,
|
|
||||||
notifications,
|
|
||||||
Layout,
|
|
||||||
Input,
|
|
||||||
Body,
|
|
||||||
Page,
|
|
||||||
} from "@budibase/bbui"
|
|
||||||
import { onMount } from "svelte"
|
|
||||||
import api from "builderStore/api"
|
|
||||||
|
|
||||||
const ConfigTypes = {
|
|
||||||
Google: "google",
|
|
||||||
// Github: "github",
|
|
||||||
// AzureAD: "ad",
|
|
||||||
}
|
|
||||||
|
|
||||||
const ConfigFields = {
|
|
||||||
Google: ["clientID", "clientSecret", "callbackURL"],
|
|
||||||
}
|
|
||||||
|
|
||||||
let google
|
|
||||||
|
|
||||||
async function save(doc) {
|
|
||||||
try {
|
|
||||||
// Save an oauth config
|
|
||||||
const response = await api.post(`/api/admin/configs`, doc)
|
|
||||||
const json = await response.json()
|
|
||||||
if (response.status !== 200) throw new Error(json.message)
|
|
||||||
google._rev = json._rev
|
|
||||||
google._id = json._id
|
|
||||||
|
|
||||||
notifications.success(`Settings saved.`)
|
|
||||||
} catch (err) {
|
|
||||||
notifications.error(`Failed to update OAuth settings. ${err}`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onMount(async () => {
|
|
||||||
// fetch the configs for oauth
|
|
||||||
const googleResponse = await api.get(
|
|
||||||
`/api/admin/configs/${ConfigTypes.Google}`
|
|
||||||
)
|
|
||||||
const googleDoc = await googleResponse.json()
|
|
||||||
|
|
||||||
if (!googleDoc._id) {
|
|
||||||
google = {
|
|
||||||
type: ConfigTypes.Google,
|
|
||||||
config: {},
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
google = googleDoc
|
|
||||||
}
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<Page>
|
|
||||||
<header>
|
|
||||||
<Heading size="M">OAuth</Heading>
|
|
||||||
<Body size="S">
|
|
||||||
Every budibase app comes with basic authentication (email/password)
|
|
||||||
included. You can add additional authentication methods from the options
|
|
||||||
below.
|
|
||||||
</Body>
|
|
||||||
</header>
|
|
||||||
<Divider />
|
|
||||||
{#if google}
|
|
||||||
<div class="config-form">
|
|
||||||
<Layout gap="S">
|
|
||||||
<Heading size="S">
|
|
||||||
<span>
|
|
||||||
<GoogleLogo />
|
|
||||||
Google
|
|
||||||
</span>
|
|
||||||
</Heading>
|
|
||||||
{#each ConfigFields.Google as field}
|
|
||||||
<div class="form-row">
|
|
||||||
<Label>{field}</Label>
|
|
||||||
<Input bind:value={google.config[field]} />
|
|
||||||
</div>
|
|
||||||
{/each}
|
|
||||||
</Layout>
|
|
||||||
<Button primary on:click={() => save(google)}>Save</Button>
|
|
||||||
</div>
|
|
||||||
<Divider />
|
|
||||||
{/if}
|
|
||||||
</Page>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.config-form {
|
|
||||||
margin-top: 42px;
|
|
||||||
margin-bottom: 42px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-row {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 20% 1fr;
|
|
||||||
grid-gap: var(--spacing-l);
|
|
||||||
}
|
|
||||||
|
|
||||||
span {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: var(--spacing-s);
|
|
||||||
}
|
|
||||||
|
|
||||||
header {
|
|
||||||
margin-bottom: 42px;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -51,45 +51,55 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
async function openUpdateRolesModal({ detail }) {
|
async function openUpdateRolesModal({ detail }) {
|
||||||
console.log(detail)
|
|
||||||
selectedApp = detail
|
selectedApp = detail
|
||||||
editRolesModal.show()
|
editRolesModal.show()
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Layout noPadding gap="XS">
|
<Layout noPadding>
|
||||||
<div class="back">
|
<Layout gap="XS" noPadding>
|
||||||
<ActionButton on:click={() => $goto("./")} quiet size="S" icon="BackAndroid"
|
<div class="back">
|
||||||
>Back to users</ActionButton
|
<ActionButton
|
||||||
>
|
on:click={() => $goto("./")}
|
||||||
</div>
|
quiet
|
||||||
<div class="heading">
|
size="S"
|
||||||
<Layout noPadding gap="XS">
|
icon="BackAndroid"
|
||||||
<Heading>User: {$roleFetch?.data?.email}</Heading>
|
>
|
||||||
<Body
|
Back to users
|
||||||
>Lorem ipsum dolor sit amet consectetur adipisicing elit. Debitis porro
|
</ActionButton>
|
||||||
ut nesciunt ipsam perspiciatis aliquam et hic minus alias beatae. Odit
|
</div>
|
||||||
veritatis quos quas laborum magnam tenetur perspiciatis ex hic.
|
<Heading>User: {$roleFetch?.data?.email}</Heading>
|
||||||
</Body>
|
<Body>
|
||||||
</Layout>
|
Lorem ipsum dolor sit amet consectetur adipisicing elit. Debitis porro ut
|
||||||
</div>
|
nesciunt ipsam perspiciatis aliquam et hic minus alias beatae. Odit
|
||||||
|
veritatis quos quas laborum magnam tenetur perspiciatis ex hic.
|
||||||
|
</Body>
|
||||||
|
</Layout>
|
||||||
<Divider size="S" />
|
<Divider size="S" />
|
||||||
<div class="general">
|
<Layout gap="S" noPadding>
|
||||||
<Heading size="S">General</Heading>
|
<Heading size="S">General</Heading>
|
||||||
<div class="fields">
|
<div class="fields">
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<Label size="L">Email</Label>
|
<Label size="L">Email</Label>
|
||||||
<Input disabled thin value={$roleFetch?.data?.email} />
|
<Input disabled thin value={$roleFetch?.data?.email} />
|
||||||
</div>
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<Label size="L">First name</Label>
|
||||||
|
<Input disabled thin value={$roleFetch?.data?.firstName} />
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<Label size="L">Last name</Label>
|
||||||
|
<Input disabled thin value={$roleFetch?.data?.lastName} />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="regenerate">
|
<div class="regenerate">
|
||||||
<ActionButton size="S" icon="Refresh" quiet
|
<ActionButton size="S" icon="Refresh" quiet>
|
||||||
>Regenerate password</ActionButton
|
Regenerate password
|
||||||
>
|
</ActionButton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</Layout>
|
||||||
<Divider size="S" />
|
<Divider size="S" />
|
||||||
<div class="roles">
|
<Layout gap="S" noPadding>
|
||||||
<Heading size="S">Configure roles</Heading>
|
<Heading size="S">Configure roles</Heading>
|
||||||
<Table
|
<Table
|
||||||
on:click={openUpdateRolesModal}
|
on:click={openUpdateRolesModal}
|
||||||
|
@ -100,16 +110,14 @@
|
||||||
allowSelectRows={false}
|
allowSelectRows={false}
|
||||||
customRenderers={[{ column: "role", component: TagsRenderer }]}
|
customRenderers={[{ column: "role", component: TagsRenderer }]}
|
||||||
/>
|
/>
|
||||||
</div>
|
</Layout>
|
||||||
<Divider size="S" />
|
<Divider size="S" />
|
||||||
<div class="delete">
|
<Layout gap="XS" noPadding>
|
||||||
<Layout gap="S" noPadding
|
<Heading size="S">Delete user</Heading>
|
||||||
><Heading size="S">Delete user</Heading>
|
<Body>Deleting a user completely removes them from your account.</Body>
|
||||||
<Body>Deleting a user completely removes them from your account.</Body>
|
</Layout>
|
||||||
<div class="delete-button">
|
<div class="delete-button">
|
||||||
<Button warning on:click={deleteUserModal.show}>Delete user</Button>
|
<Button warning on:click={deleteUserModal.show}>Delete user</Button>
|
||||||
</div></Layout
|
|
||||||
>
|
|
||||||
</div>
|
</div>
|
||||||
</Layout>
|
</Layout>
|
||||||
|
|
||||||
|
@ -122,10 +130,9 @@
|
||||||
cancelText="Cancel"
|
cancelText="Cancel"
|
||||||
showCloseIcon={false}
|
showCloseIcon={false}
|
||||||
>
|
>
|
||||||
<Body
|
<Body>
|
||||||
>Are you sure you want to delete <strong>{$roleFetch?.data?.email}</strong
|
Are you sure you want to delete <strong>{$roleFetch?.data?.email}</strong>
|
||||||
></Body
|
</Body>
|
||||||
>
|
|
||||||
</ModalContent>
|
</ModalContent>
|
||||||
</Modal>
|
</Modal>
|
||||||
<Modal bind:this={editRolesModal}>
|
<Modal bind:this={editRolesModal}>
|
||||||
|
@ -140,26 +147,12 @@
|
||||||
.fields {
|
.fields {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-gap: var(--spacing-m);
|
grid-gap: var(--spacing-m);
|
||||||
margin-top: var(--spacing-xl);
|
|
||||||
}
|
}
|
||||||
.field {
|
.field {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 32% 1fr;
|
grid-template-columns: 32% 1fr;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
.heading {
|
|
||||||
margin-bottom: var(--spacing-xl);
|
|
||||||
}
|
|
||||||
.general {
|
|
||||||
position: relative;
|
|
||||||
margin: var(--spacing-xl) 0;
|
|
||||||
}
|
|
||||||
.roles {
|
|
||||||
margin: var(--spacing-xl) 0;
|
|
||||||
}
|
|
||||||
.delete {
|
|
||||||
margin-top: var(--spacing-xl);
|
|
||||||
}
|
|
||||||
.regenerate {
|
.regenerate {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
|
|
|
@ -30,18 +30,18 @@
|
||||||
<ModalContent
|
<ModalContent
|
||||||
onConfirm={createUserFlow}
|
onConfirm={createUserFlow}
|
||||||
size="M"
|
size="M"
|
||||||
title="Add new user options"
|
title="Add new user"
|
||||||
confirmText="Add user"
|
confirmText="Add user"
|
||||||
confirmDisabled={disabled}
|
confirmDisabled={disabled}
|
||||||
cancelText="Cancel"
|
cancelText="Cancel"
|
||||||
disabled={$error}
|
disabled={$error}
|
||||||
showCloseIcon={false}
|
showCloseIcon={false}
|
||||||
>
|
>
|
||||||
<Body noPadding
|
<Body size="S">
|
||||||
>If you have SMTP configured and an email for the new user, you can use the
|
If you have SMTP configured and an email for the new user, you can use the
|
||||||
automated email onboarding flow. Otherwise, use our basic onboarding process
|
automated email onboarding flow. Otherwise, use our basic onboarding process
|
||||||
with autogenerated passwords.</Body
|
with autogenerated passwords.
|
||||||
>
|
</Body>
|
||||||
<Select
|
<Select
|
||||||
placeholder={null}
|
placeholder={null}
|
||||||
bind:value={selected}
|
bind:value={selected}
|
||||||
|
|
|
@ -26,10 +26,10 @@
|
||||||
error={$touched && $error}
|
error={$touched && $error}
|
||||||
showCloseIcon={false}
|
showCloseIcon={false}
|
||||||
>
|
>
|
||||||
<Body noPadding
|
<Body size="S">
|
||||||
>Below you will find the user’s username and password. The password will not
|
Below you will find the user’s username and password. The password will not
|
||||||
be accessible from this point. Please download the credentials.</Body
|
be accessible from this point. Please save the credentials.
|
||||||
>
|
</Body>
|
||||||
<Input
|
<Input
|
||||||
type="email"
|
type="email"
|
||||||
label="Username"
|
label="Username"
|
||||||
|
|
|
@ -4,8 +4,9 @@
|
||||||
|
|
||||||
const displayLimit = 5
|
const displayLimit = 5
|
||||||
|
|
||||||
$: tags = value?.slice(0, displayLimit) ?? []
|
$: roles = value?.filter(role => role != null) ?? []
|
||||||
$: leftover = (value?.length ?? 0) - tags.length
|
$: tags = roles.slice(0, displayLimit)
|
||||||
|
$: leftover = roles.length - tags.length
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Tags>
|
<Tags>
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
<script>
|
<script>
|
||||||
import { createEventDispatcher } from "svelte"
|
import { createEventDispatcher } from "svelte"
|
||||||
import { Body, Select, ModalContent, notifications } from "@budibase/bbui"
|
import { Body, Select, ModalContent, notifications } from "@budibase/bbui"
|
||||||
import { fetchData } from "helpers"
|
|
||||||
import { users } from "stores/portal"
|
import { users } from "stores/portal"
|
||||||
|
|
||||||
export let app
|
export let app
|
||||||
|
@ -11,7 +10,7 @@
|
||||||
|
|
||||||
const roles = app.roles
|
const roles = app.roles
|
||||||
let options = roles.map(role => role._id)
|
let options = roles.map(role => role._id)
|
||||||
let selectedRole
|
let selectedRole = user?.roles?.[app?._id]
|
||||||
|
|
||||||
async function updateUserRoles() {
|
async function updateUserRoles() {
|
||||||
const res = await users.updateRoles({
|
const res = await users.updateRoles({
|
||||||
|
@ -24,7 +23,7 @@
|
||||||
if (res.status === 400) {
|
if (res.status === 400) {
|
||||||
notifications.error("Failed to update role")
|
notifications.error("Failed to update role")
|
||||||
} else {
|
} else {
|
||||||
notifications.success("Roles updated")
|
notifications.success("Role updated")
|
||||||
dispatch("update")
|
dispatch("update")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -32,20 +31,20 @@
|
||||||
|
|
||||||
<ModalContent
|
<ModalContent
|
||||||
onConfirm={updateUserRoles}
|
onConfirm={updateUserRoles}
|
||||||
title="Update App Roles"
|
title="Update App Role"
|
||||||
confirmText="Update roles"
|
confirmText="Update role"
|
||||||
cancelText="Cancel"
|
cancelText="Cancel"
|
||||||
size="M"
|
size="M"
|
||||||
showCloseIcon={false}
|
showCloseIcon={false}
|
||||||
>
|
>
|
||||||
<Body noPadding
|
<Body>
|
||||||
>Update {user.email}'s roles for <strong>{app.name}</strong>.</Body
|
Update {user.email}'s role for <strong>{app.name}</strong>.
|
||||||
>
|
</Body>
|
||||||
<Select
|
<Select
|
||||||
placeholder={null}
|
placeholder={null}
|
||||||
bind:value={selectedRole}
|
bind:value={selectedRole}
|
||||||
on:change
|
on:change
|
||||||
{options}
|
{options}
|
||||||
label="Select roles:"
|
label="Role"
|
||||||
/>
|
/>
|
||||||
</ModalContent>
|
</ModalContent>
|
||||||
|
|
|
@ -44,28 +44,27 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Layout>
|
<Layout>
|
||||||
<div class="heading">
|
<Layout gap="XS" noPadding>
|
||||||
<Heading>Users</Heading>
|
<Heading>Users</Heading>
|
||||||
<Body
|
<Body>
|
||||||
>Users are the common denominator in Budibase. Each user is assigned to a
|
Users are the common denominator in Budibase. Each user is assigned to a
|
||||||
group that contains apps and permissions. In this section, you can add
|
group that contains apps and permissions. In this section, you can add
|
||||||
users, or edit and delete an existing user.</Body
|
users, or edit and delete an existing user.
|
||||||
>
|
</Body>
|
||||||
</div>
|
</Layout>
|
||||||
<Divider size="S" />
|
<Divider size="S" />
|
||||||
|
<Layout gap="S" noPadding>
|
||||||
<div class="users">
|
<div class="users-heading">
|
||||||
<Heading size="S">Users</Heading>
|
<Heading size="S">Users</Heading>
|
||||||
<div class="field">
|
|
||||||
<Label size="L">Search / filter</Label>
|
|
||||||
<Search bind:value={search} placeholder="" />
|
|
||||||
</div>
|
|
||||||
<div class="buttons">
|
|
||||||
<ButtonGroup>
|
<ButtonGroup>
|
||||||
<Button disabled secondary>Import users</Button>
|
<Button disabled secondary>Import users</Button>
|
||||||
<Button primary on:click={createUserModal.show}>Add user</Button>
|
<Button primary on:click={createUserModal.show}>Add user</Button>
|
||||||
</ButtonGroup>
|
</ButtonGroup>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<Label size="L">Search / filter</Label>
|
||||||
|
<Search bind:value={search} placeholder="" />
|
||||||
|
</div>
|
||||||
<Table
|
<Table
|
||||||
on:click={({ detail }) => $goto(`./${detail._id}`)}
|
on:click={({ detail }) => $goto(`./${detail._id}`)}
|
||||||
{schema}
|
{schema}
|
||||||
|
@ -75,31 +74,28 @@
|
||||||
allowSelectRows={false}
|
allowSelectRows={false}
|
||||||
customRenderers={[{ column: "group", component: TagsRenderer }]}
|
customRenderers={[{ column: "group", component: TagsRenderer }]}
|
||||||
/>
|
/>
|
||||||
</div>
|
</Layout>
|
||||||
</Layout>
|
</Layout>
|
||||||
|
|
||||||
<Modal bind:this={createUserModal}
|
<Modal bind:this={createUserModal}>
|
||||||
><AddUserModal on:change={openBasicOnoboardingModal} /></Modal
|
<AddUserModal on:change={openBasicOnoboardingModal} />
|
||||||
>
|
</Modal>
|
||||||
<Modal bind:this={basicOnboardingModal}><BasicOnboardingModal {email} /></Modal>
|
<Modal bind:this={basicOnboardingModal}><BasicOnboardingModal {email} /></Modal>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.users {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
.field {
|
.field {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
grid-gap: var(--spacing-m);
|
grid-gap: var(--spacing-m);
|
||||||
margin: var(--spacing-xl) 0;
|
|
||||||
}
|
}
|
||||||
.field > :global(*) + :global(*) {
|
.field > :global(*) + :global(*) {
|
||||||
margin-left: var(--spacing-m);
|
margin-left: var(--spacing-m);
|
||||||
}
|
}
|
||||||
.buttons {
|
.users-heading {
|
||||||
position: absolute;
|
display: flex;
|
||||||
top: 0;
|
flex-direction: row;
|
||||||
right: 0;
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,43 +0,0 @@
|
||||||
<svg
|
|
||||||
width="18"
|
|
||||||
height="18"
|
|
||||||
viewBox="0 0 268 268"
|
|
||||||
fill="none"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
>
|
|
||||||
<g clip-path="url(#clip0)">
|
|
||||||
<path
|
|
||||||
d="M58.8037 109.043C64.0284 93.2355 74.1116 79.4822 87.615 69.7447C101.118
|
|
||||||
60.0073 117.352 54.783 134 54.8172C152.872 54.8172 169.934 61.5172 183.334
|
|
||||||
72.4828L222.328 33.5C198.566 12.7858 168.114 0 134 0C81.1817 0 35.711
|
|
||||||
30.1277 13.8467 74.2583L58.8037 109.043Z"
|
|
||||||
fill="#EA4335"
|
|
||||||
/>
|
|
||||||
<path
|
|
||||||
d="M179.113 201.145C166.942 208.995 151.487 213.183 134 213.183C117.418
|
|
||||||
213.217 101.246 208.034 87.7727 198.369C74.2993 188.703 64.2077 175.044
|
|
||||||
58.9265 159.326L13.8132 193.574C24.8821 215.978 42.012 234.828 63.2572
|
|
||||||
247.984C84.5024 261.14 109.011 268.075 134 268C166.752 268 198.041 256.353
|
|
||||||
221.48 234.5L179.125 201.145H179.113Z"
|
|
||||||
fill="#34A853"
|
|
||||||
/>
|
|
||||||
<path
|
|
||||||
d="M221.48 234.5C245.991 211.631 261.903 177.595 261.903 134C261.903
|
|
||||||
126.072 260.686 117.552 258.866 109.634H134V161.414H205.869C202.329
|
|
||||||
178.823 192.804 192.301 179.125 201.145L221.48 234.5Z"
|
|
||||||
fill="#4A90E2"
|
|
||||||
/>
|
|
||||||
<path
|
|
||||||
d="M58.9265 159.326C56.1947 151.162 54.8068 142.609 54.8172 134C54.8172
|
|
||||||
125.268 56.213 116.882 58.8037 109.043L13.8467 74.2584C4.64957 92.825
|
|
||||||
-0.0915078 113.28 1.86708e-05 134C1.86708e-05 155.44 4.96919 175.652
|
|
||||||
13.8132 193.574L58.9265 159.326Z"
|
|
||||||
fill="#FBBC05"
|
|
||||||
/>
|
|
||||||
</g>
|
|
||||||
<defs>
|
|
||||||
<clipPath id="clip0">
|
|
||||||
<rect width="268" height="268" fill="white" />
|
|
||||||
</clipPath>
|
|
||||||
</defs>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 1.5 KiB |
|
@ -1,114 +0,0 @@
|
||||||
<script>
|
|
||||||
import GoogleLogo from "./_logos/Google.svelte"
|
|
||||||
import {
|
|
||||||
Button,
|
|
||||||
Page,
|
|
||||||
Heading,
|
|
||||||
Divider,
|
|
||||||
Label,
|
|
||||||
notifications,
|
|
||||||
Layout,
|
|
||||||
Input,
|
|
||||||
Body,
|
|
||||||
} from "@budibase/bbui"
|
|
||||||
import { onMount } from "svelte"
|
|
||||||
import api from "builderStore/api"
|
|
||||||
|
|
||||||
const ConfigTypes = {
|
|
||||||
Google: "google",
|
|
||||||
// Github: "github",
|
|
||||||
// AzureAD: "ad",
|
|
||||||
}
|
|
||||||
|
|
||||||
const ConfigFields = {
|
|
||||||
Google: ["clientID", "clientSecret", "callbackURL"],
|
|
||||||
}
|
|
||||||
|
|
||||||
let google
|
|
||||||
|
|
||||||
async function save(doc) {
|
|
||||||
try {
|
|
||||||
// Save an oauth config
|
|
||||||
const response = await api.post(`/api/admin/configs`, doc)
|
|
||||||
const json = await response.json()
|
|
||||||
if (response.status !== 200) throw new Error(json.message)
|
|
||||||
google._rev = json._rev
|
|
||||||
google._id = json._id
|
|
||||||
|
|
||||||
notifications.success(`Settings saved.`)
|
|
||||||
} catch (err) {
|
|
||||||
notifications.error(`Failed to update OAuth settings. ${err}`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onMount(async () => {
|
|
||||||
// fetch the configs for oauth
|
|
||||||
const googleResponse = await api.get(
|
|
||||||
`/api/admin/configs/${ConfigTypes.Google}`
|
|
||||||
)
|
|
||||||
const googleDoc = await googleResponse.json()
|
|
||||||
|
|
||||||
if (!googleDoc._id) {
|
|
||||||
google = {
|
|
||||||
type: ConfigTypes.Google,
|
|
||||||
config: {},
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
google = googleDoc
|
|
||||||
}
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<Page>
|
|
||||||
<Layout noPadding>
|
|
||||||
<div>
|
|
||||||
<Heading size="M">OAuth</Heading>
|
|
||||||
<Body>
|
|
||||||
Every budibase app comes with basic authentication (email/password)
|
|
||||||
included. You can add additional authentication methods from the options
|
|
||||||
below.
|
|
||||||
</Body>
|
|
||||||
</div>
|
|
||||||
<Divider />
|
|
||||||
{#if google}
|
|
||||||
<div>
|
|
||||||
<Heading size="S">
|
|
||||||
<span>
|
|
||||||
<GoogleLogo />
|
|
||||||
Google
|
|
||||||
</span>
|
|
||||||
</Heading>
|
|
||||||
<Body>
|
|
||||||
To allow users to authenticate using their Google accounts, fill out
|
|
||||||
the fields below.
|
|
||||||
</Body>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{#each ConfigFields.Google as field}
|
|
||||||
<div class="form-row">
|
|
||||||
<Label size="L">{field}</Label>
|
|
||||||
<Input bind:value={google.config[field]} />
|
|
||||||
</div>
|
|
||||||
{/each}
|
|
||||||
<div>
|
|
||||||
<Button cta on:click={() => save(google)}>Save</Button>
|
|
||||||
</div>
|
|
||||||
<Divider />
|
|
||||||
{/if}
|
|
||||||
</Layout>
|
|
||||||
</Page>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.form-row {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 20% 1fr;
|
|
||||||
grid-gap: var(--spacing-l);
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
span {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: var(--spacing-s);
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -57,78 +57,76 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="container">
|
<Layout>
|
||||||
<Layout noPadding>
|
<Layout gap="XS" noPadding>
|
||||||
<div class="intro">
|
<Heading size="M">General</Heading>
|
||||||
<Heading size="M">General</Heading>
|
<Body>
|
||||||
<Body>
|
General is the place where you edit your organisation name, logo. You can
|
||||||
General is the place where you edit your organisation name, logo. You
|
also configure your platform URL as well as turn on or off analytics.
|
||||||
can also configure your platform URL as well as turn on or off
|
</Body>
|
||||||
analytics.
|
</Layout>
|
||||||
</Body>
|
<Divider size="S" />
|
||||||
|
<Layout gap="XS" noPadding>
|
||||||
|
<Heading size="S">Information</Heading>
|
||||||
|
<Body size="S">Here you can update your logo and organization name.</Body>
|
||||||
|
</Layout>
|
||||||
|
<div class="fields">
|
||||||
|
<div class="field">
|
||||||
|
<Label size="L">Organization name</Label>
|
||||||
|
<Input thin bind:value={$organisation.company} />
|
||||||
</div>
|
</div>
|
||||||
<Divider size="S" />
|
<div class="field logo">
|
||||||
<div class="information">
|
<Label size="L">Logo</Label>
|
||||||
<Heading size="S">Information</Heading>
|
<div class="file">
|
||||||
<Body>Here you can update your logo and organization name.</Body>
|
<Dropzone
|
||||||
<div class="fields">
|
value={[file]}
|
||||||
<div class="field">
|
on:change={e => {
|
||||||
<Label size="L">Organization name</Label>
|
file = e.detail?.[0]
|
||||||
<Input thin bind:value={$organisation.company} />
|
}}
|
||||||
</div>
|
/>
|
||||||
<div class="field logo">
|
|
||||||
<Label size="L">Logo</Label>
|
|
||||||
<div class="file">
|
|
||||||
<Dropzone
|
|
||||||
value={[file]}
|
|
||||||
on:change={e => {
|
|
||||||
file = e.detail?.[0]
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Divider size="S" />
|
</div>
|
||||||
<div class="analytics">
|
<Divider size="S" />
|
||||||
<Heading size="S">Platform</Heading>
|
<Layout gap="XS" noPadding>
|
||||||
<Body>Here you can set up general platform settings.</Body>
|
<Heading size="S">Platform</Heading>
|
||||||
<div class="fields">
|
<Body size="S">Here you can set up general platform settings.</Body>
|
||||||
<div class="field">
|
</Layout>
|
||||||
<Label size="L">Platform URL</Label>
|
<div class="fields">
|
||||||
<Input thin bind:value={$organisation.platformUrl} />
|
<div class="field">
|
||||||
</div>
|
<Label size="L">Platform URL</Label>
|
||||||
</div>
|
<Input thin bind:value={$organisation.platformUrl} />
|
||||||
</div>
|
</div>
|
||||||
<Divider size="S" />
|
</div>
|
||||||
<div class="analytics">
|
<Divider size="S" />
|
||||||
|
<Layout gap="S" noPadding>
|
||||||
|
<Layout gap="XS" noPadding>
|
||||||
<Heading size="S">Analytics</Heading>
|
<Heading size="S">Analytics</Heading>
|
||||||
<Body>
|
<Body size="S">
|
||||||
If you would like to send analytics that help us make Budibase better,
|
If you would like to send analytics that help us make Budibase better,
|
||||||
please let us know below.
|
please let us know below.
|
||||||
</Body>
|
</Body>
|
||||||
<div class="fields">
|
</Layout>
|
||||||
<div class="field">
|
<div class="fields">
|
||||||
<Label size="L">Send Analytics to Budibase</Label>
|
<div class="field">
|
||||||
<Toggle text="" value={!analyticsDisabled} />
|
<Label size="L">Send Analytics to Budibase</Label>
|
||||||
</div>
|
<Toggle text="" value={!analyticsDisabled} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="save">
|
|
||||||
<Button disabled={loading} on:click={saveConfig} cta>Save</Button>
|
|
||||||
</div>
|
|
||||||
</Layout>
|
</Layout>
|
||||||
</div>
|
<div>
|
||||||
|
<Button disabled={loading} on:click={saveConfig} cta>Save</Button>
|
||||||
|
</div>
|
||||||
|
</Layout>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.fields {
|
.fields {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-gap: var(--spacing-m);
|
grid-gap: var(--spacing-m);
|
||||||
margin-top: var(--spacing-xl);
|
|
||||||
}
|
}
|
||||||
.field {
|
.field {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 32% 1fr;
|
grid-template-columns: 33% 1fr;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
.file {
|
.file {
|
||||||
|
@ -137,10 +135,4 @@
|
||||||
.logo {
|
.logo {
|
||||||
align-items: start;
|
align-items: start;
|
||||||
}
|
}
|
||||||
.intro {
|
|
||||||
display: grid;
|
|
||||||
}
|
|
||||||
.save {
|
|
||||||
margin-left: auto;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -33,6 +33,25 @@ export function createAuthStore() {
|
||||||
await response.json()
|
await response.json()
|
||||||
store.update(state => ({ ...state, user: null }))
|
store.update(state => ({ ...state, user: null }))
|
||||||
},
|
},
|
||||||
|
forgotPassword: async email => {
|
||||||
|
const response = await api.post(`/api/admin/auth/reset`, {
|
||||||
|
email,
|
||||||
|
})
|
||||||
|
if (response.status !== 200) {
|
||||||
|
throw "Unable to send email with reset link"
|
||||||
|
}
|
||||||
|
await response.json()
|
||||||
|
},
|
||||||
|
resetPassword: async (password, code) => {
|
||||||
|
const response = await api.post(`/api/admin/auth/reset/update`, {
|
||||||
|
password,
|
||||||
|
resetCode: code,
|
||||||
|
})
|
||||||
|
if (response.status !== 200) {
|
||||||
|
throw "Unable to reset password"
|
||||||
|
}
|
||||||
|
await response.json()
|
||||||
|
},
|
||||||
createUser: async user => {
|
createUser: async user => {
|
||||||
const response = await api.post(`/api/admin/users`, user)
|
const response = await api.post(`/api/admin/users`, user)
|
||||||
if (response.status !== 200) {
|
if (response.status !== 200) {
|
||||||
|
|
|
@ -50,6 +50,24 @@ exports.USERS_TABLE_SCHEMA = {
|
||||||
fieldName: "email",
|
fieldName: "email",
|
||||||
name: "email",
|
name: "email",
|
||||||
},
|
},
|
||||||
|
firstName: {
|
||||||
|
name: "firstName",
|
||||||
|
fieldName: "firstName",
|
||||||
|
type: exports.FieldTypes.STRING,
|
||||||
|
constraints: {
|
||||||
|
type: exports.FieldTypes.STRING,
|
||||||
|
presence: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
lastName: {
|
||||||
|
name: "lastName",
|
||||||
|
fieldName: "lastName",
|
||||||
|
type: exports.FieldTypes.STRING,
|
||||||
|
constraints: {
|
||||||
|
type: exports.FieldTypes.STRING,
|
||||||
|
presence: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
roleId: {
|
roleId: {
|
||||||
fieldName: "roleId",
|
fieldName: "roleId",
|
||||||
name: "roleId",
|
name: "roleId",
|
||||||
|
|
|
@ -54,10 +54,13 @@ exports.reset = async ctx => {
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const user = await getGlobalUserByEmail(email)
|
const user = await getGlobalUserByEmail(email)
|
||||||
await sendEmail(email, EmailTemplatePurpose.PASSWORD_RECOVERY, {
|
// only if user exists, don't error though if they don't
|
||||||
user,
|
if (user) {
|
||||||
subject: "{{ company }} platform password reset",
|
await sendEmail(email, EmailTemplatePurpose.PASSWORD_RECOVERY, {
|
||||||
})
|
user,
|
||||||
|
subject: "{{ company }} platform password reset",
|
||||||
|
})
|
||||||
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// don't throw any kind of error to the user, this might give away something
|
// don't throw any kind of error to the user, this might give away something
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,93 +0,0 @@
|
||||||
const authPkg = require("@budibase/auth")
|
|
||||||
const { google } = require("@budibase/auth/src/middleware")
|
|
||||||
const { Configs } = require("../../constants")
|
|
||||||
const CouchDB = require("../../db")
|
|
||||||
const { clearCookie } = authPkg.utils
|
|
||||||
const { Cookies } = authPkg.constants
|
|
||||||
const { passport } = authPkg.auth
|
|
||||||
|
|
||||||
const GLOBAL_DB = authPkg.StaticDatabases.GLOBAL.name
|
|
||||||
|
|
||||||
exports.authenticate = async (ctx, next) => {
|
|
||||||
return passport.authenticate("local", async (err, user) => {
|
|
||||||
if (err) {
|
|
||||||
return ctx.throw(403, "Unauthorized")
|
|
||||||
}
|
|
||||||
|
|
||||||
const expires = new Date()
|
|
||||||
expires.setDate(expires.getDate() + 1)
|
|
||||||
|
|
||||||
if (!user) {
|
|
||||||
return ctx.throw(403, "Unauthorized")
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.cookies.set(Cookies.Auth, user.token, {
|
|
||||||
expires,
|
|
||||||
path: "/",
|
|
||||||
httpOnly: false,
|
|
||||||
overwrite: true,
|
|
||||||
})
|
|
||||||
|
|
||||||
delete user.token
|
|
||||||
|
|
||||||
ctx.body = { user }
|
|
||||||
})(ctx, next)
|
|
||||||
}
|
|
||||||
|
|
||||||
exports.logout = async ctx => {
|
|
||||||
clearCookie(ctx, Cookies.Auth)
|
|
||||||
ctx.body = { message: "User logged out" }
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The initial call that google authentication makes to take you to the google login screen.
|
|
||||||
* On a successful login, you will be redirected to the googleAuth callback route.
|
|
||||||
*/
|
|
||||||
exports.googlePreAuth = async (ctx, next) => {
|
|
||||||
const db = new CouchDB(GLOBAL_DB)
|
|
||||||
const config = await authPkg.db.getScopedFullConfig(db, {
|
|
||||||
type: Configs.GOOGLE,
|
|
||||||
group: ctx.query.group,
|
|
||||||
})
|
|
||||||
const strategy = await google.strategyFactory(config)
|
|
||||||
|
|
||||||
return passport.authenticate(strategy, {
|
|
||||||
scope: ["profile", "email"],
|
|
||||||
})(ctx, next)
|
|
||||||
}
|
|
||||||
|
|
||||||
exports.googleAuth = async (ctx, next) => {
|
|
||||||
const db = new CouchDB(GLOBAL_DB)
|
|
||||||
|
|
||||||
const config = await authPkg.db.getScopedFullConfig(db, {
|
|
||||||
type: Configs.GOOGLE,
|
|
||||||
group: ctx.query.group,
|
|
||||||
})
|
|
||||||
const strategy = await google.strategyFactory(config)
|
|
||||||
|
|
||||||
return passport.authenticate(
|
|
||||||
strategy,
|
|
||||||
{ successRedirect: "/", failureRedirect: "/error" },
|
|
||||||
async (err, user) => {
|
|
||||||
if (err) {
|
|
||||||
return ctx.throw(403, "Unauthorized")
|
|
||||||
}
|
|
||||||
|
|
||||||
const expires = new Date()
|
|
||||||
expires.setDate(expires.getDate() + 1)
|
|
||||||
|
|
||||||
if (!user) {
|
|
||||||
return ctx.throw(403, "Unauthorized")
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.cookies.set(Cookies.Auth, user.token, {
|
|
||||||
expires,
|
|
||||||
path: "/",
|
|
||||||
httpOnly: false,
|
|
||||||
overwrite: true,
|
|
||||||
})
|
|
||||||
|
|
||||||
ctx.redirect("/")
|
|
||||||
}
|
|
||||||
)(ctx, next)
|
|
||||||
}
|
|
|
@ -117,6 +117,10 @@ async function getSmtpConfiguration(db, groupId = null) {
|
||||||
* @return {Promise<boolean>} returns true if there is a configuration that can be used.
|
* @return {Promise<boolean>} returns true if there is a configuration that can be used.
|
||||||
*/
|
*/
|
||||||
exports.isEmailConfigured = async (groupId = null) => {
|
exports.isEmailConfigured = async (groupId = null) => {
|
||||||
|
// when "testing" simply return true
|
||||||
|
if (TEST_MODE) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
const db = new CouchDB(GLOBAL_DB)
|
const db = new CouchDB(GLOBAL_DB)
|
||||||
const config = await getSmtpConfiguration(db, groupId)
|
const config = await getSmtpConfiguration(db, groupId)
|
||||||
return config != null
|
return config != null
|
||||||
|
|
|
@ -35,7 +35,7 @@ exports.getSettingsTemplateContext = async (purpose, code = null) => {
|
||||||
case EmailTemplatePurpose.PASSWORD_RECOVERY:
|
case EmailTemplatePurpose.PASSWORD_RECOVERY:
|
||||||
context[InternalTemplateBindings.RESET_CODE] = code
|
context[InternalTemplateBindings.RESET_CODE] = code
|
||||||
context[InternalTemplateBindings.RESET_URL] = checkSlashesInUrl(
|
context[InternalTemplateBindings.RESET_URL] = checkSlashesInUrl(
|
||||||
`${URL}/reset?code=${code}`
|
`${URL}/builder/auth/reset?code=${code}`
|
||||||
)
|
)
|
||||||
break
|
break
|
||||||
case EmailTemplatePurpose.INVITATION:
|
case EmailTemplatePurpose.INVITATION:
|
||||||
|
|
Loading…
Reference in New Issue