Changed sign-up flow to require e-mail verification up front, preventing account existence leakage.

This commit is contained in:
2026-07-23 22:12:33 +02:00
parent 18c30b6f98
commit 6b3866a99f
12 changed files with 387 additions and 79 deletions

View File

@@ -3,32 +3,29 @@ import VueHCaptcha from '@hcaptcha/vue3-hcaptcha';
import { inject, onMounted, ref } from 'vue';
import Password from '#/components/form/password.vue';
import Ellipsis from '#/components/loading/ellipsis.vue';
import { post } from '#/src/api.js';
import navigate from '#/src/navigate.js';
const pageContext = inject('pageContext');
const { user, env } = pageContext;
const { token } = pageContext.urlParsed.search;
const username = ref('');
const email = ref('');
const password = ref('');
const passwordConfirm = ref('');
const errorMsg = ref(null);
const submitted = ref(false);
const userInput = ref(null);
const success = ref(false);
const mainInput = ref(null);
const captcha = ref(null);
async function signup() {
errorMsg.value = null;
submitted.value = true;
if (password.value !== passwordConfirm.value) {
errorMsg.value = 'Passwords do not match';
return;
}
if (env.captcha.enabled && !captcha.value) {
errorMsg.value = 'Please complete the CAPTCHA';
return;
@@ -36,11 +33,14 @@ async function signup() {
try {
const newUser = await post('/users', {
token,
username: username.value,
email: email.value,
password: password.value,
redirect: pageContext.urlParsed.search.r,
captcha: captcha.value,
}, {
successFeedback: 'Sign-up successful!',
appendErrorMessage: true,
});
navigate(`/user/${newUser.username}`, null, { redirect: true });
@@ -53,8 +53,30 @@ async function signup() {
}
}
async function request() {
errorMsg.value = null;
submitted.value = true;
try {
await post('/signups', {
email: email.value,
}, {
successFeedback: 'Sign-up requested, please check your inbox',
appendErrorMessage: true,
});
success.value = true;
}
catch (error) {
errorMsg.value = error.message;
}
finally {
submitted.value = false;
}
}
onMounted(() => {
userInput.value.focus();
mainInput.value.focus();
});
</script>
@@ -88,7 +110,7 @@ onMounted(() => {
</div>
<form
v-else
v-else-if="token"
autocomplete="off"
class="login-panel"
@submit.prevent="signup"
@@ -99,34 +121,19 @@ onMounted(() => {
>{{ errorMsg }}</div>
<input
ref="userInput"
ref="mainInput"
v-model="username"
placeholder="Username"
class="input"
required
>
<input
v-model="email"
type="email"
placeholder="E-mail"
class="input"
required
>
<Password
:password="password"
autocomplete="new-password"
@input="(newPassword) => password = newPassword"
/>
<Password
:password="passwordConfirm"
autocomplete="new-password"
placeholder="Confirm password"
@input="(newPassword) => passwordConfirm = newPassword"
/>
<VueHCaptcha
v-if="env.captcha.enabled"
:sitekey="env.captcha.siteKey"
@@ -135,15 +142,53 @@ onMounted(() => {
@expired="captcha = null"
/>
<Ellipsis v-if="submitted" />
<button
v-else
class="button button-submit"
:disabled="submitted"
>Sign up</button>
</form>
<a
href="/login"
class="link"
>I already have an account</a>
<form
v-else
autocomplete="off"
class="login-panel"
@submit.prevent="request"
>
<span
v-if="success"
class="success"
>
<strong class="success-heading">Thank you for signing up!</strong>
Please check your e-mail inbox to continue.
</span>
<template v-else>
<input
ref="mainInput"
v-model="email"
type="email"
placeholder="E-mail"
class="input"
required
>
<Ellipsis v-if="submitted" />
<template v-else>
<button
class="button button-submit"
:disabled="submitted"
>Send signup e-mail</button>
<a
href="/login"
class="link login"
>I already have an account</a>
</template>
</template>
</form>
</div>
</template>
@@ -179,7 +224,7 @@ onMounted(() => {
}
.link {
margin-top: .5rem;
margin-top: 1rem;
text-align: center;
}
}
@@ -235,4 +280,18 @@ onMounted(() => {
font-weight: bold;
text-align: center;
}
.success {
line-height: 1.25;
}
.success-heading {
display: block;
color: var(--primary);
margin-bottom: .5rem;
}
.load-container {
justify-content: center;
}
</style>