Added password recovery and login attempt guards.

This commit is contained in:
2026-07-23 04:40:25 +02:00
parent ef9be8ef9a
commit 6f68a50eb7
17 changed files with 541 additions and 49 deletions

View File

@@ -0,0 +1,218 @@
<script setup>
import { inject, ref } from 'vue';
import Password from '#/components/form/password.vue';
import Ellipsis from '#/components/loading/ellipsis.vue';
import { post } from '#/src/api.js';
const pageContext = inject('pageContext');
const { userId, token } = pageContext.urlParsed.search;
const allowRecovery = pageContext.env.emailEnabled;
const credential = ref('');
const submitted = ref(false);
const success = ref(false);
const errorMsg = ref(null);
const password = ref(null);
async function request() {
submitted.value = true;
errorMsg.value = null;
try {
await post('/passwords/request', {
credential: credential.value,
}, {
successFeedback: 'Password reset requested, please check your e-mail inbox',
appendErrorMessage: true,
});
success.value = true;
}
catch (error) {
errorMsg.value = error.message;
}
finally {
submitted.value = false;
}
}
async function reset() {
submitted.value = true;
errorMsg.value = null;
try {
await post('/passwords/reset', {
userId,
token,
password: password.value,
}, {
successFeedback: 'Password reset successfully',
appendErrorMessage: true,
});
success.value = true;
}
catch (error) {
errorMsg.value = error.message;
}
finally {
submitted.value = false;
}
}
</script>
<template>
<div class="recovery-container">
<h3 class="heading">
<template v-if="userId && token">Password reset</template>
<template v-else>Account recovery</template>
</h3>
<template v-if="allowRecovery">
<strong
v-if="errorMsg"
class="error"
>{{ errorMsg }}</strong>
<form
v-else-if="userId && token"
class="login-panel"
@submit.prevent="reset"
>
<div
v-if="success"
class="success"
>
<strong class="success-heading">Password reset successfully</strong>
<a
href="/login"
class="link"
>Go to login</a>
</div>
<template v-else>
<Password
:password="password"
@input="(newPassword) => password = newPassword"
/>
<Ellipsis v-if="submitted" />
<button
v-else
class="button button-submit"
>Reset password</button>
</template>
</form>
<form
v-else-if="allowRecovery"
class="login-panel"
@submit.prevent="request"
>
<div
v-if="success"
class="success"
>
<strong class="success-heading">Password reset requested</strong>
If the account exists, instructions have been sent to the associated e-mail address.
</div>
<template v-else>
<input
v-model="credential"
placeholder="Username or e-mail"
class="input"
>
<Ellipsis v-if="submitted" />
<button
v-else
class="button button-submit"
>Request password reset</button>
</template>
</form>
</template>
<span
v-else
class="error"
>Password recovery is currently unavailable</span>
</div>
</template>
<style scoped>
.input {
width: 100%;
}
.heading {
text-align: center;
margin: 1rem;
}
.recovery-container {
display: flex;
flex-direction: column;
flex-grow: 1;
justify-content: center;
align-items: center;
background: var(--background-base-10);
}
.login-panel {
width: 20rem;
max-width: 100%;
display: flex;
flex-direction: column;
gap: .5rem;
padding: 1rem;
margin: 0 1rem 1rem 1rem;
border-radius: .5rem;
box-shadow: 0 0 3px var(--shadow-weak-30);
background: var(--background-base);
font-size: 1rem;
.button {
justify-content: center;
}
.link {
margin-top: .5rem;
text-align: center;
}
}
.error {
padding: 1rem;
color: var(--error);
}
.success {
justify-content: center;
line-height: 1.25;
text-align: center;
.link {
display: block;
margin-top: 1rem;
text-align: center;
font-weight: bold;
}
}
.success-heading {
display: block;
margin-bottom: .5rem;
}
.load-container {
justify-content: center;
padding: 1rem 1rem .5rem 1rem;
}
</style>