mirror of
https://github.com/coracle-social/flotilla.git
synced 2025-12-10 10:57:04 +00:00
Add email confirmation and password reset
This commit is contained in:
@@ -4,15 +4,25 @@
|
|||||||
import Landing from "@app/components/Landing.svelte"
|
import Landing from "@app/components/Landing.svelte"
|
||||||
import Toast from "@app/components/Toast.svelte"
|
import Toast from "@app/components/Toast.svelte"
|
||||||
import PrimaryNav from "@app/components/PrimaryNav.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 {BURROW_URL} from "@app/state"
|
||||||
import {modals, pushModal} from "@app/modal"
|
import {modals, pushModal} from "@app/modal"
|
||||||
|
|
||||||
if (BURROW_URL && $page.route.id === "/confirm-email") {
|
if (BURROW_URL && !$pubkey) {
|
||||||
pushModal(SignUpConfirm, {
|
if ($page.url.pathname === "/confirm-email") {
|
||||||
email: $page.url.searchParams.get("email"),
|
pushModal(EmailConfirm, {
|
||||||
token: $page.url.searchParams.get("token"),
|
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>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
import {BURROW_URL} from "@app/state"
|
import {BURROW_URL} from "@app/state"
|
||||||
|
|
||||||
export let email
|
export let email
|
||||||
export let token
|
export let confirm_token
|
||||||
|
|
||||||
const login = () => {
|
const login = () => {
|
||||||
pushModal(LogInPassword, {email}, {path: "/"})
|
pushModal(LogInPassword, {email}, {path: "/"})
|
||||||
@@ -19,7 +19,7 @@
|
|||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
const [res] = await Promise.all([
|
const [res] = await Promise.all([
|
||||||
postJson(BURROW_URL + "/user/confirm", {email, token}),
|
postJson(BURROW_URL + "/user/confirm-email", {email, confirm_token}),
|
||||||
sleep(2000),
|
sleep(2000),
|
||||||
])
|
])
|
||||||
|
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
import {onMount, onDestroy} from "svelte"
|
import {onMount, onDestroy} from "svelte"
|
||||||
import {postJson, stripProtocol} from "@welshman/lib"
|
import {postJson, stripProtocol} from "@welshman/lib"
|
||||||
import {Nip46Broker, makeSecret} from "@welshman/signer"
|
import {Nip46Broker, makeSecret} from "@welshman/signer"
|
||||||
|
import {normalizeRelayUrl} from "@welshman/util"
|
||||||
import {addSession} from "@welshman/app"
|
import {addSession} from "@welshman/app"
|
||||||
import Spinner from "@lib/components/Spinner.svelte"
|
import Spinner from "@lib/components/Spinner.svelte"
|
||||||
import Button from "@lib/components/Button.svelte"
|
import Button from "@lib/components/Button.svelte"
|
||||||
@@ -9,8 +10,9 @@
|
|||||||
import Icon from "@lib/components/Icon.svelte"
|
import Icon from "@lib/components/Icon.svelte"
|
||||||
import ModalHeader from "@lib/components/ModalHeader.svelte"
|
import ModalHeader from "@lib/components/ModalHeader.svelte"
|
||||||
import ModalFooter from "@lib/components/ModalFooter.svelte"
|
import ModalFooter from "@lib/components/ModalFooter.svelte"
|
||||||
|
import PasswordResetRequest from "@app/components/PasswordResetRequest.svelte"
|
||||||
import {loadUserData} from "@app/commands"
|
import {loadUserData} from "@app/commands"
|
||||||
import {clearModals} from "@app/modal"
|
import {clearModals, pushModal} from "@app/modal"
|
||||||
import {setChecked} from "@app/notifications"
|
import {setChecked} from "@app/notifications"
|
||||||
import {pushToast} from "@app/toast"
|
import {pushToast} from "@app/toast"
|
||||||
import {NIP46_PERMS, BURROW_URL, PLATFORM_URL, PLATFORM_NAME, PLATFORM_LOGO} from "@app/state"
|
import {NIP46_PERMS, BURROW_URL, PLATFORM_URL, PLATFORM_NAME, PLATFORM_LOGO} from "@app/state"
|
||||||
@@ -19,9 +21,11 @@
|
|||||||
|
|
||||||
const clientSecret = makeSecret()
|
const clientSecret = makeSecret()
|
||||||
|
|
||||||
|
const startReset = () => pushModal(PasswordResetRequest, {email})
|
||||||
|
|
||||||
const abortController = new AbortController()
|
const abortController = new AbortController()
|
||||||
|
|
||||||
const relays = ["ws://" + stripProtocol(BURROW_URL)]
|
const relays = [normalizeRelayUrl("ws://" + stripProtocol(BURROW_URL))]
|
||||||
|
|
||||||
const broker = Nip46Broker.get({clientSecret, relays})
|
const broker = Nip46Broker.get({clientSecret, relays})
|
||||||
|
|
||||||
@@ -30,10 +34,15 @@
|
|||||||
const onSubmit = async () => {
|
const onSubmit = async () => {
|
||||||
loading = true
|
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) {
|
if (res.error) {
|
||||||
pushToast({message: res.error, theme: "error"})
|
pushToast({message: res.error, theme: "error"})
|
||||||
|
loading = false
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
pushToast({message: "Something went wrong, please try again!", theme: "error"})
|
||||||
loading = false
|
loading = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -91,7 +100,7 @@
|
|||||||
<form class="column gap-4" on:submit|preventDefault={onSubmit}>
|
<form class="column gap-4" on:submit|preventDefault={onSubmit}>
|
||||||
<ModalHeader>
|
<ModalHeader>
|
||||||
<div slot="title">Log In</div>
|
<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>
|
</ModalHeader>
|
||||||
<FieldInline>
|
<FieldInline>
|
||||||
<p slot="label">Email</p>
|
<p slot="label">Email</p>
|
||||||
@@ -107,9 +116,10 @@
|
|||||||
<input bind:value={password} type="password" />
|
<input bind:value={password} type="password" />
|
||||||
</label>
|
</label>
|
||||||
</FieldInline>
|
</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
|
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>
|
</p>
|
||||||
<ModalFooter>
|
<ModalFooter>
|
||||||
<Button class="btn btn-link" on:click={back} disabled={loading}>
|
<Button class="btn btn-link" on:click={back} disabled={loading}>
|
||||||
|
|||||||
@@ -39,7 +39,7 @@
|
|||||||
<div slot="info">Learn about {PLATFORM_NAME} and support the developer</div>
|
<div slot="info">Learn about {PLATFORM_NAME} and support the developer</div>
|
||||||
</CardButton>
|
</CardButton>
|
||||||
</Link>
|
</Link>
|
||||||
<Button on:click={logout} class="btn btn-error">
|
<Button on:click={logout} class="btn btn-neutral">
|
||||||
<Icon icon="exit" /> Log Out
|
<Icon icon="exit" /> Log Out
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
61
src/app/components/PasswordReset.svelte
Normal file
61
src/app/components/PasswordReset.svelte
Normal 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>
|
||||||
62
src/app/components/PasswordResetRequest.svelte
Normal file
62
src/app/components/PasswordResetRequest.svelte
Normal 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>
|
||||||
@@ -1 +0,0 @@
|
|||||||
hi
|
|
||||||
Reference in New Issue
Block a user