Merge branch 'next' of github.com:Budibase/budibase into user-app-list
This commit is contained in:
commit
6e5cfe8520
|
@ -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}
|
||||
>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
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>
|
||||
|
|
|
@ -12,7 +12,6 @@
|
|||
</Link>
|
||||
</div>
|
||||
|
||||
|
||||
<style>
|
||||
.outer {
|
||||
border: 1px solid #494949;
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
*/
|
||||
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
|
||||
|
|
|
@ -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:
|
||||
|
|
Loading…
Reference in New Issue