Merge pull request #1501 from Budibase/feature/forgot-password
Forgotten password flow (builder)
This commit is contained in:
commit
85e6472c3a
|
@ -6,10 +6,11 @@
|
||||||
export let gap = "M"
|
export let gap = "M"
|
||||||
export let noGap = false
|
export let noGap = false
|
||||||
export let alignContent = "normal"
|
export let alignContent = "normal"
|
||||||
|
export let justifyItems = "stretch"
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
style="align-content:{alignContent};"
|
style="align-content:{alignContent};justify-items:{justifyItems};"
|
||||||
class:horizontal
|
class:horizontal
|
||||||
class="container paddingX-{!noPadding && paddingX} paddingY-{!noPadding &&
|
class="container paddingX-{!noPadding && paddingX} paddingY-{!noPadding &&
|
||||||
paddingY} gap-{!noGap && gap}"
|
paddingY} gap-{!noGap && gap}"
|
||||||
|
|
|
@ -4,9 +4,11 @@
|
||||||
export let size = "M"
|
export let size = "M"
|
||||||
export let serif = false
|
export let serif = false
|
||||||
export let noPadding = false
|
export let noPadding = false
|
||||||
|
export let textAlign
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<p
|
<p
|
||||||
|
style="{textAlign ? `text-align:${textAlign}` : ``}"
|
||||||
class:noPadding
|
class:noPadding
|
||||||
class="spectrum-Body spectrum-Body--size{size}"
|
class="spectrum-Body spectrum-Body--size{size}"
|
||||||
class:spectrum-Body--serif={serif}
|
class:spectrum-Body--serif={serif}
|
||||||
|
|
|
@ -3,8 +3,14 @@
|
||||||
|
|
||||||
// Sizes
|
// Sizes
|
||||||
export let size = "M"
|
export let size = "M"
|
||||||
|
export let textAlign
|
||||||
|
export let noPadding = false
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<h1 class="spectrum-Heading spectrum-Heading--size{size}">
|
<h1
|
||||||
|
style="{textAlign ? `text-align:${textAlign}` : ``}"
|
||||||
|
class:noPadding
|
||||||
|
class="spectrum-Heading spectrum-Heading--size{size}"
|
||||||
|
>
|
||||||
<slot />
|
<slot />
|
||||||
</h1>
|
</h1>
|
||||||
|
|
Binary file not shown.
After Width: | Height: | Size: 11 KiB |
|
@ -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>
|
|
@ -0,0 +1,60 @@
|
||||||
|
<script>
|
||||||
|
import {
|
||||||
|
Input,
|
||||||
|
Button,
|
||||||
|
Layout,
|
||||||
|
Body,
|
||||||
|
Heading,
|
||||||
|
notifications,
|
||||||
|
} from "@budibase/bbui"
|
||||||
|
import { auth } from "stores/backend"
|
||||||
|
|
||||||
|
let email = ""
|
||||||
|
|
||||||
|
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">
|
||||||
|
<div class="main">
|
||||||
|
<Layout>
|
||||||
|
<Layout noPadding justifyItems="center">
|
||||||
|
<img src="https://i.imgur.com/ZKyklgF.png" />
|
||||||
|
</Layout>
|
||||||
|
<Layout gap="XS" noPadding>
|
||||||
|
<Heading textAlign="center">Forgotten your password?</Heading>
|
||||||
|
<Body size="S" textAlign="center">
|
||||||
|
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={email} />
|
||||||
|
</Layout>
|
||||||
|
<Button cta on:click={forgot} disabled={!email}>Reset your password</Button>
|
||||||
|
</Layout>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.login {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main {
|
||||||
|
width: 300px;
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: 48px;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,43 @@
|
||||||
|
<script>
|
||||||
|
import { Link } from "@budibase/bbui"
|
||||||
|
import GoogleLogo from "/assets/google-logo.png"
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="outer">
|
||||||
|
<Link target="_blank" href="/api/admin/auth/google">
|
||||||
|
<div class="inner">
|
||||||
|
<img src={GoogleLogo} alt="google icon" />
|
||||||
|
<p>Sign in with Google</p>
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.outer {
|
||||||
|
border: 1px solid #494949;
|
||||||
|
border-radius: 4px;
|
||||||
|
width: 100%;
|
||||||
|
background-color: var(--background-alt);
|
||||||
|
}
|
||||||
|
.inner {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding-top: var(--spacing-xs);
|
||||||
|
padding-bottom: var(--spacing-xs);
|
||||||
|
}
|
||||||
|
.inner img {
|
||||||
|
width: 18px;
|
||||||
|
margin: 3px 10px 3px 3px;
|
||||||
|
}
|
||||||
|
.inner p {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
.outer :global(a) {
|
||||||
|
text-decoration: none;
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: var(--font-size-m);
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -2,11 +2,15 @@
|
||||||
import { goto } from "@roxi/routify"
|
import { goto } from "@roxi/routify"
|
||||||
import {
|
import {
|
||||||
notifications,
|
notifications,
|
||||||
Link,
|
|
||||||
Input,
|
Input,
|
||||||
Modal,
|
Button,
|
||||||
ModalContent,
|
Divider,
|
||||||
|
ActionButton,
|
||||||
|
Layout,
|
||||||
|
Body,
|
||||||
|
Heading,
|
||||||
} from "@budibase/bbui"
|
} from "@budibase/bbui"
|
||||||
|
import GoogleButton from "./GoogleButton.svelte"
|
||||||
import { auth } from "stores/backend"
|
import { auth } from "stores/backend"
|
||||||
|
|
||||||
let username = ""
|
let username = ""
|
||||||
|
@ -27,33 +31,50 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Modal fixed>
|
<div class="login">
|
||||||
<ModalContent
|
<div class="main">
|
||||||
size="M"
|
<Layout>
|
||||||
title="Log In"
|
<Layout noPadding justifyItems="center">
|
||||||
onConfirm={login}
|
<img src="https://i.imgur.com/ZKyklgF.png" />
|
||||||
confirmText="Log In"
|
<Heading>Sign in to Budibase</Heading>
|
||||||
showCancelButton={false}
|
</Layout>
|
||||||
showCloseIcon={false}
|
<GoogleButton />
|
||||||
>
|
<Layout gap="XS" noPadding>
|
||||||
<Input label="Email" bind:value={username} />
|
<Divider noGrid />
|
||||||
<Input label="Password" type="password" on:change bind:value={password} />
|
<Body size="S" textAlign="center">Sign in with email</Body>
|
||||||
<div class="footer" slot="footer">
|
<Input label="Email" bind:value={username} />
|
||||||
<Link target="_blank" href="/api/admin/auth/google">
|
<Input
|
||||||
Sign In With Google
|
label="Password"
|
||||||
</Link>
|
type="password"
|
||||||
</div>
|
on:change
|
||||||
</ModalContent>
|
bind:value={password}
|
||||||
</Modal>
|
/>
|
||||||
|
</Layout>
|
||||||
|
<Layout gap="S" noPadding>
|
||||||
|
<Button cta on:click={login}>Sign in to Budibase</Button>
|
||||||
|
<ActionButton quiet on:click={() => $goto("./forgot")}>
|
||||||
|
Forgot password?
|
||||||
|
</ActionButton>
|
||||||
|
</Layout>
|
||||||
|
</Layout>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.footer {
|
.login {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: column;
|
||||||
justify-content: flex-end;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
.footer :global(a) {
|
|
||||||
margin-right: var(--spectrum-global-dimension-static-size-200);
|
.main {
|
||||||
|
width: 300px;
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: 48px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -0,0 +1,58 @@
|
||||||
|
<script>
|
||||||
|
import { Button, Layout, Body, Heading, notifications } from "@budibase/bbui"
|
||||||
|
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, error
|
||||||
|
|
||||||
|
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">
|
||||||
|
<div class="main">
|
||||||
|
<Layout>
|
||||||
|
<Layout noPadding justifyItems="center">
|
||||||
|
<img src="https://i.imgur.com/ZKyklgF.png" />
|
||||||
|
</Layout>
|
||||||
|
<Layout gap="XS" noPadding>
|
||||||
|
<Heading textAlign="center">Reset your password</Heading>
|
||||||
|
<Body size="S" textAlign="center">
|
||||||
|
Please enter the new password you'd like to use.
|
||||||
|
</Body>
|
||||||
|
<PasswordRepeatInput bind:password bind:error />
|
||||||
|
</Layout>
|
||||||
|
<Button cta on:click={reset} disabled={error || !resetCode}>Reset your password</Button>
|
||||||
|
</Layout>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.login {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main {
|
||||||
|
width: 260px;
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: 48px;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -1,6 +1,6 @@
|
||||||
<script>
|
<script>
|
||||||
import { onMount } from "svelte"
|
import { onMount } from "svelte"
|
||||||
import { page, goto } from "@roxi/routify"
|
import { goto, isActive } from "@roxi/routify"
|
||||||
import { auth } from "stores/backend"
|
import { auth } from "stores/backend"
|
||||||
import { admin } from "stores/portal"
|
import { admin } from "stores/portal"
|
||||||
|
|
||||||
|
@ -23,10 +23,11 @@
|
||||||
// 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 (
|
if (
|
||||||
!$page.path.includes("/builder/invite") &&
|
|
||||||
loaded &&
|
loaded &&
|
||||||
hasAdminUser &&
|
hasAdminUser &&
|
||||||
!$auth.user
|
!$auth.user &&
|
||||||
|
!$isActive("./auth") &&
|
||||||
|
!$isActive("./invite")
|
||||||
) {
|
) {
|
||||||
$goto("./auth/login")
|
$goto("./auth/login")
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
<script>
|
||||||
|
import ForgotForm from "components/login/ForgotForm.svelte"
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<ForgotForm />
|
|
@ -0,0 +1,5 @@
|
||||||
|
<script>
|
||||||
|
import ResetForm from "components/login/ResetForm.svelte"
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<ResetForm />
|
|
@ -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 PasswordRepeatInput from "components/common/users/PasswordRepeatInput.svelte"
|
||||||
import { users } from "stores/portal"
|
import { users } from "stores/portal"
|
||||||
|
|
||||||
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)
|
||||||
}
|
}
|
||||||
|
@ -40,33 +26,15 @@
|
||||||
<Layout gap="XS">
|
<Layout gap="XS">
|
||||||
<img src="https://i.imgur.com/ZKyklgF.png" />
|
<img src="https://i.imgur.com/ZKyklgF.png" />
|
||||||
</Layout>
|
</Layout>
|
||||||
<div class="center">
|
|
||||||
<Layout gap="XS">
|
|
||||||
<Heading size="M">Accept Invitation</Heading>
|
|
||||||
<Body size="M">Please enter a password to setup your user.</Body>
|
|
||||||
</Layout>
|
|
||||||
</div>
|
|
||||||
<Layout gap="XS">
|
<Layout gap="XS">
|
||||||
<Input
|
<Heading textAlign="center" size="M">Accept Invitation</Heading>
|
||||||
label="Password"
|
<Body textAlign="center" size="S"
|
||||||
type="password"
|
>Please enter a password to setup your user.</Body
|
||||||
error={$passwordTouched && $passwordError}
|
>
|
||||||
bind:value={$password}
|
<PasswordRepeatInput bind:error bind:password />
|
||||||
/>
|
|
||||||
<Input
|
|
||||||
label="Repeat Password"
|
|
||||||
type="password"
|
|
||||||
error={$repeatTouched &&
|
|
||||||
$password !== $repeat &&
|
|
||||||
"Passwords must match"}
|
|
||||||
bind:value={$repeat}
|
|
||||||
/>
|
|
||||||
</Layout>
|
</Layout>
|
||||||
<Layout gap="S">
|
<Layout gap="S">
|
||||||
<Button
|
<Button disabled={error} cta on:click={acceptInvite}>Accept invite</Button
|
||||||
disabled={!$passwordTouched || !$repeatTouched || $password !== $repeat}
|
|
||||||
cta
|
|
||||||
on:click={acceptInvite}>Accept invite</Button
|
|
||||||
>
|
>
|
||||||
</Layout>
|
</Layout>
|
||||||
</div>
|
</div>
|
||||||
|
@ -87,9 +55,6 @@
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
}
|
}
|
||||||
.center {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
img {
|
img {
|
||||||
width: 40px;
|
width: 40px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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