Merge branch 'next' of github.com:Budibase/budibase into user-app-list

This commit is contained in:
Andrew Kingston 2021-05-19 10:09:20 +01:00
commit 6e5cfe8520
13 changed files with 140 additions and 158 deletions

View File

@ -3,12 +3,15 @@
export let size = "M" export let size = "M"
export let serif = false export let serif = false
export let weight = 400 export let weight = null
export let textAlign = "left" export let textAlign = null
</script> </script>
<p <p
style="font-weight:{weight};text-align:{textAlign};" style={`
${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}
> >

View File

@ -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
style="{textAlign ? `text-align:${textAlign}` : ``}"
class:noPadding class:noPadding
class="spectrum-Heading spectrum-Heading--size{size}"> class="spectrum-Heading spectrum-Heading--size{size}"
>
<slot /> <slot />
</h1> </h1>

View File

@ -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>

View File

@ -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>

View File

@ -12,7 +12,6 @@
</Link> </Link>
</div> </div>
<style> <style>
.outer { .outer {
border: 1px solid #494949; border: 1px solid #494949;

View File

@ -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>

View File

@ -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")
} }
} }

View File

@ -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)
} }
@ -45,27 +31,8 @@
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>

View File

@ -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) {

View File

@ -54,10 +54,13 @@ exports.reset = async ctx => {
} }
try { try {
const user = await getGlobalUserByEmail(email) const user = await getGlobalUserByEmail(email)
// only if user exists, don't error though if they don't
if (user) {
await sendEmail(email, EmailTemplatePurpose.PASSWORD_RECOVERY, { await sendEmail(email, EmailTemplatePurpose.PASSWORD_RECOVERY, {
user, user,
subject: "{{ company }} platform password reset", 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
} }

View File

@ -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)
}

View File

@ -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

View File

@ -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: