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 f1a7f2a440
13 changed files with 140 additions and 158 deletions

View File

@ -3,12 +3,15 @@
export let size = "M"
export let serif = false
export let weight = 400
export let textAlign = "left"
export let weight = null
export let textAlign = null
</script>
<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--serif={serif}
>

View File

@ -3,12 +3,14 @@
// Sizes
export let size = "M"
export let textAlign = "left"
export let textAlign
export let noPadding = false
</script>
<h1 style="text-align:{textAlign};"
class:noPadding
class="spectrum-Heading spectrum-Heading--size{size}">
<h1
style="{textAlign ? `text-align:${textAlign}` : ``}"
class:noPadding
class="spectrum-Heading spectrum-Heading--size{size}"
>
<slot />
</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>
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 { auth } from "stores/backend"
let username = ""
let password = ""
let email = ""
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>
<div class="login">
@ -20,9 +34,11 @@
No problem! Just enter your account's email address and we'll send you
a link to reset it.
</Body>
<Input label="Email" bind:value={username} />
<Input label="Email" bind:value={email} />
</Layout>
<Button cta on:click={reset}>Reset your password</Button>
<Button cta on:click={forgot} disabled={!email}>
Reset your password
</Button>
</Layout>
</div>
</div>

View File

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

View File

@ -1,12 +1,23 @@
<script>
import { Input, Button, Layout, Body, Heading } from "@budibase/bbui"
import { params } from "@roxi/routify"
import { notifications, Button, Layout, Body, Heading } from "@budibase/bbui"
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"]
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>
<div class="login">
@ -20,9 +31,11 @@
<Body size="S" textAlign="center">
Please enter the new password you'd like to use.
</Body>
<Input label="Password" bind:value={password} />
<PasswordRepeatInput bind:password bind:error />
</Layout>
<Button cta on:click={reset}>Reset your password</Button>
<Button cta on:click={reset} disabled={error || !resetCode}>
Reset your password
</Button>
</Layout>
</div>
</div>

View File

@ -22,7 +22,13 @@
// 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")
}
}

View File

@ -1,29 +1,15 @@
<script>
import {
Layout,
Heading,
Body,
Input,
Button,
notifications,
} from "@budibase/bbui"
import { Layout, Heading, Body, Button, notifications } from "@budibase/bbui"
import { goto, params } from "@roxi/routify"
import { createValidationStore, requiredValidator } from "helpers/validation"
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"]
let password, error
async function acceptInvite() {
try {
const res = await users.acceptInvite(inviteCode, $password)
const res = await users.acceptInvite(inviteCode, password)
if (!res) {
throw new Error(res.message)
}
@ -45,27 +31,8 @@
Please enter a password to set up your user.
</Body>
</Layout>
<Layout gap="XS" noPadding>
<Input
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}
>
<PasswordRepeatInput bind:error bind:password />
<Button disabled={error} cta on:click={acceptInvite}>
Accept invite
</Button>
</Layout>

View File

@ -33,6 +33,25 @@ export function createAuthStore() {
await response.json()
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 => {
const response = await api.post(`/api/admin/users`, user)
if (response.status !== 200) {

View File

@ -54,10 +54,13 @@ exports.reset = async ctx => {
}
try {
const user = await getGlobalUserByEmail(email)
await sendEmail(email, EmailTemplatePurpose.PASSWORD_RECOVERY, {
user,
subject: "{{ company }} platform password reset",
})
// only if user exists, don't error though if they don't
if (user) {
await sendEmail(email, EmailTemplatePurpose.PASSWORD_RECOVERY, {
user,
subject: "{{ company }} platform password reset",
})
}
} catch (err) {
// 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.
*/
exports.isEmailConfigured = async (groupId = null) => {
// when "testing" simply return true
if (TEST_MODE) {
return true
}
const db = new CouchDB(GLOBAL_DB)
const config = await getSmtpConfiguration(db, groupId)
return config != null

View File

@ -35,7 +35,7 @@ exports.getSettingsTemplateContext = async (purpose, code = null) => {
case EmailTemplatePurpose.PASSWORD_RECOVERY:
context[InternalTemplateBindings.RESET_CODE] = code
context[InternalTemplateBindings.RESET_URL] = checkSlashesInUrl(
`${URL}/reset?code=${code}`
`${URL}/builder/auth/reset?code=${code}`
)
break
case EmailTemplatePurpose.INVITATION: