Add email confirmation and password reset

This commit is contained in:
Jon Staab
2024-11-27 11:19:38 -08:00
parent c2aa829334
commit 1ea39c1d56
7 changed files with 160 additions and 18 deletions

View File

@@ -4,15 +4,25 @@
import Landing from "@app/components/Landing.svelte"
import Toast from "@app/components/Toast.svelte"
import PrimaryNav from "@app/components/PrimaryNav.svelte"
import SignUpConfirm from "@app/components/SignUpConfirm.svelte"
import EmailConfirm from "@app/components/EmailConfirm.svelte"
import PasswordReset from "@app/components/PasswordReset.svelte"
import {BURROW_URL} from "@app/state"
import {modals, pushModal} from "@app/modal"
if (BURROW_URL && $page.route.id === "/confirm-email") {
pushModal(SignUpConfirm, {
email: $page.url.searchParams.get("email"),
token: $page.url.searchParams.get("token"),
})
if (BURROW_URL && !$pubkey) {
if ($page.url.pathname === "/confirm-email") {
pushModal(EmailConfirm, {
email: $page.url.searchParams.get("email"),
confirm_token: $page.url.searchParams.get("confirm_token"),
})
}
if ($page.url.pathname === "/reset-password") {
pushModal(PasswordReset, {
email: $page.url.searchParams.get("email"),
reset_token: $page.url.searchParams.get("reset_token"),
})
}
}
</script>

View File

@@ -8,7 +8,7 @@
import {BURROW_URL} from "@app/state"
export let email
export let token
export let confirm_token
const login = () => {
pushModal(LogInPassword, {email}, {path: "/"})
@@ -19,7 +19,7 @@
onMount(async () => {
const [res] = await Promise.all([
postJson(BURROW_URL + "/user/confirm", {email, token}),
postJson(BURROW_URL + "/user/confirm-email", {email, confirm_token}),
sleep(2000),
])

View File

@@ -2,6 +2,7 @@
import {onMount, onDestroy} from "svelte"
import {postJson, stripProtocol} from "@welshman/lib"
import {Nip46Broker, makeSecret} from "@welshman/signer"
import {normalizeRelayUrl} from "@welshman/util"
import {addSession} from "@welshman/app"
import Spinner from "@lib/components/Spinner.svelte"
import Button from "@lib/components/Button.svelte"
@@ -9,8 +10,9 @@
import Icon from "@lib/components/Icon.svelte"
import ModalHeader from "@lib/components/ModalHeader.svelte"
import ModalFooter from "@lib/components/ModalFooter.svelte"
import PasswordResetRequest from "@app/components/PasswordResetRequest.svelte"
import {loadUserData} from "@app/commands"
import {clearModals} from "@app/modal"
import {clearModals, pushModal} from "@app/modal"
import {setChecked} from "@app/notifications"
import {pushToast} from "@app/toast"
import {NIP46_PERMS, BURROW_URL, PLATFORM_URL, PLATFORM_NAME, PLATFORM_LOGO} from "@app/state"
@@ -19,9 +21,11 @@
const clientSecret = makeSecret()
const startReset = () => pushModal(PasswordResetRequest, {email})
const abortController = new AbortController()
const relays = ["ws://" + stripProtocol(BURROW_URL)]
const relays = [normalizeRelayUrl("ws://" + stripProtocol(BURROW_URL))]
const broker = Nip46Broker.get({clientSecret, relays})
@@ -30,10 +34,15 @@
const onSubmit = async () => {
loading = true
const res = await postJson(BURROW_URL + "/session", {email, password, nostrconnect: url})
try {
const res = await postJson(BURROW_URL + "/session", {email, password, nostrconnect: url})
if (res.error) {
pushToast({message: res.error, theme: "error"})
if (res.error) {
pushToast({message: res.error, theme: "error"})
loading = false
}
} catch (e) {
pushToast({message: "Something went wrong, please try again!", theme: "error"})
loading = false
}
}
@@ -91,7 +100,7 @@
<form class="column gap-4" on:submit|preventDefault={onSubmit}>
<ModalHeader>
<div slot="title">Log In</div>
<div slot="info">Log in using your email and password.</div>
<div slot="info">Log in using your email and password</div>
</ModalHeader>
<FieldInline>
<p slot="label">Email</p>
@@ -107,9 +116,10 @@
<input bind:value={password} type="password" />
</label>
</FieldInline>
<p class="text-sm opacity-75">
<p class="text-sm">
Your email and password only work to log in to {PLATFORM_NAME}. To use your key on other nostr
applications, visit your settings page.
applications, visit your settings page. <Button class="link" on:click={startReset}
>Forgot your password?</Button>
</p>
<ModalFooter>
<Button class="btn btn-link" on:click={back} disabled={loading}>

View File

@@ -39,7 +39,7 @@
<div slot="info">Learn about {PLATFORM_NAME} and support the developer</div>
</CardButton>
</Link>
<Button on:click={logout} class="btn btn-error">
<Button on:click={logout} class="btn btn-neutral">
<Icon icon="exit" /> Log Out
</Button>
</div>

View File

@@ -0,0 +1,61 @@
<script lang="ts">
import {postJson, sleep} from "@welshman/lib"
import Button from "@lib/components/Button.svelte"
import Spinner from "@lib/components/Spinner.svelte"
import Icon from "@lib/components/Icon.svelte"
import FieldInline from "@lib/components/FieldInline.svelte"
import ModalHeader from "@lib/components/ModalHeader.svelte"
import LogInPassword from "@app/components/LogInPassword.svelte"
import {pushModal} from "@app/modal"
import {pushToast} from "@app/toast"
import {BURROW_URL} from "@app/state"
export let email
export let reset_token
const onSubmit = async () => {
loading = true
try {
const [res] = await Promise.all([
postJson(BURROW_URL + "/user/confirm-reset", {email, password, reset_token}),
sleep(1000),
])
if (res.error) {
pushToast({message: res.error, theme: "error"})
} else {
pushToast({message: "Password reset successfully!"})
pushModal(LogInPassword, {email}, {path: "/"})
}
} finally {
loading = false
}
}
let loading = false
let password = ""
</script>
<form class="column gap-4" on:submit|preventDefault={onSubmit}>
<ModalHeader>
<div slot="title">Reset your password</div>
</ModalHeader>
<FieldInline disabled={loading}>
<p slot="label">Email Address</p>
<label class="input input-bordered flex w-full items-center gap-2" slot="input">
<Icon icon="user-rounded" />
<input readonly value={email} class="grow" />
</label>
</FieldInline>
<FieldInline disabled={loading}>
<p slot="label">New Password</p>
<label class="input input-bordered flex w-full items-center gap-2" slot="input">
<Icon icon="key" />
<input bind:value={password} class="grow" type="password" />
</label>
</FieldInline>
<Button type="submit" class="btn btn-primary" disabled={loading}>
<Spinner {loading}>Reset password</Spinner>
</Button>
</form>

View File

@@ -0,0 +1,62 @@
<script lang="ts">
import {postJson, sleep} from "@welshman/lib"
import Icon from "@lib/components/Icon.svelte"
import Button from "@lib/components/Button.svelte"
import FieldInline from "@lib/components/FieldInline.svelte"
import Spinner from "@lib/components/Spinner.svelte"
import ModalHeader from "@lib/components/ModalHeader.svelte"
import ModalFooter from "@lib/components/ModalFooter.svelte"
import LogInPassword from "@app/components/LogInPassword.svelte"
import {pushModal} from "@app/modal"
import {pushToast} from "@app/toast"
import {BURROW_URL} from "@app/state"
export let email: string
const back = () => history.back()
const onSubmit = async () => {
loading = true
try {
const [res] = await Promise.all([
postJson(BURROW_URL + "/user/request-reset", {email}),
sleep(1000),
])
if (res.error) {
pushToast({message: res.error, theme: "error"})
} else {
pushToast({message: `Password reset email has been sent!`})
pushModal(LogInPassword, {email}, {path: "/"})
}
} finally {
loading = false
}
}
let loading = false
</script>
<form class="column gap-4" on:submit|preventDefault={onSubmit}>
<ModalHeader>
<div slot="title">Reset your password</div>
</ModalHeader>
<FieldInline disabled={loading}>
<p slot="label">Email Address</p>
<label class="input input-bordered flex w-full items-center gap-2" slot="input">
<Icon icon="user-rounded" />
<input bind:value={email} class="grow" />
</label>
<p slot="info">You'll be sent an email with a password reset link.</p>
</FieldInline>
<ModalFooter>
<Button class="btn btn-link" on:click={back}>
<Icon icon="alt-arrow-left" />
Go back
</Button>
<Button type="submit" class="btn btn-primary" disabled={loading}>
<Spinner {loading}>Request password reset link</Spinner>
</Button>
</ModalFooter>
</form>

View File

@@ -1 +0,0 @@
hi