fix: fix password resets by using HTTP API

This commit is contained in:
mrjvs
2026-08-12 17:45:57 +02:00
parent 6d8ca1326a
commit 82f952608c
4 changed files with 177 additions and 13 deletions

View File

@@ -2,11 +2,18 @@ import { ResetPasswordSchema } from '~~/shared/api-types';
export default defineEventHandler(async (event): Promise<void> => {
const body = await readZodBody(event, ResetPasswordSchema);
const grpc = useApiGrpc(event);
const apiFetch = useHttpApi(event);
await grpc.resetPassword({
password: body.password,
passwordConfirm: body.passwordConfirm,
token: body.resetToken
// The GRPC version requires a login token, which the user doesnt have when resetting password, so we're using the HTTP api
await apiFetch('/v1/reset-password', {
method: 'POST',
body: JSON.stringify({
password: body.password,
password_confirm: body.passwordConfirm,
token: body.resetToken
}),
headers: {
'Content-type': 'application/json'
}
});
});

View File

@@ -1,5 +1,82 @@
<script setup lang="ts">
import VueHcaptcha from '@hcaptcha/vue3-hcaptcha';
import type { ApiAuthForgotPasswordRequest } from '~~/shared/api-types';
const form = reactive({ emailOrUsername: '' });
const errorMessage = ref<string | null>();
const hcaptcha = ref<VueHcaptcha | null>(null);
// TODO style this entire page
async function submit() {
try {
const hCaptchaResponse = hcaptcha.value ? (await hcaptcha.value.executeAsync()).response : null;
await $fetch('/api/auth/forgot-password', {
method: 'POST',
body: {
emailOrPassword: form.emailOrUsername,
captchaResponse: hCaptchaResponse ?? undefined
} satisfies ApiAuthForgotPasswordRequest
});
alert('Success - check your inbox');
await navigateTo('/');
} catch (error: unknown) {
if (error === 'challenge-closed') {
// Thrown if the captcha is closed, can be safely ignored
return;
}
const err = getApiError(error);
errorMessage.value = err.code;
setTimeout(() => { // TODO: replace this toast
errorMessage.value = null;
}, 5000);
}
}
</script>
<template>
<div>
<p>TODO: forgot password stub page</p>
<div class="account-form-wrapper">
<form
class="account register"
@submit.prevent="submit"
>
<h2>Forgot password</h2>
<div>
<input
v-model="form.emailOrUsername"
type="text"
required
>
</div>
<vue-hcaptcha
v-if="$config.public.hcaptchaSiteKey"
ref="hcaptcha"
:sitekey="$config.public.hcaptchaSiteKey"
class="h-captcha"
theme="dark"
/>
<div class="buttons">
<button type="submit">
Send
</button>
</div>
</form>
</div>
<div
v-if="errorMessage"
class="banner-notice error"
>
<div>
<p>{{ errorMessage }}</p>
</div>
</div>
</div>
</template>
<style scoped>
@import "/assets/css/auth.css";
</style>

View File

@@ -115,6 +115,13 @@ async function registerSubmission() {
required
>
</div>
<vue-hcaptcha
v-if="$config.public.hcaptchaSiteKey"
ref="hcaptcha"
:sitekey="$config.public.hcaptchaSiteKey"
class="h-captcha"
theme="dark"
/>
<div class="buttons">
<button type="submit">
{{ $t("account.loginForm.register") }}
@@ -126,13 +133,6 @@ async function registerSubmission() {
</div>
</form>
</div>
<vue-hcaptcha
v-if="$config.public.hcaptchaSiteKey"
ref="invisibleHcaptcha"
:sitekey="$config.public.hcaptchaSiteKey"
class="h-captcha"
size="invisible"
/>
<div
v-if="errorMessage"
class="banner-notice error"

View File

@@ -0,0 +1,80 @@
<script setup lang="ts">
import type { ApiAuthResetPasswordRequest } from '~~/shared/api-types';
const route = useRoute();
const form = reactive({ password: '', passwordConfirm: '' });
const errorMessage = ref<string | null>();
// TODO style this entire page
async function submit() {
const resetToken = route.query.token?.toString();
try {
if (!resetToken) {
throw new Error('No reset token provided');
}
await $fetch('/api/auth/reset-password', {
method: 'POST',
body: {
password: form.password,
passwordConfirm: form.passwordConfirm,
resetToken
} satisfies ApiAuthResetPasswordRequest
});
alert('Success - your password has been changed');
await navigateTo('/account');
} catch (error: unknown) {
const err = getApiError(error);
errorMessage.value = err.code;
setTimeout(() => { // TODO: replace this toast
errorMessage.value = null;
}, 5000);
}
}
</script>
<template>
<div>
<div class="account-form-wrapper">
<form
class="account register"
@submit.prevent="submit"
>
<h2>Change password</h2>
<div>
<label>Password</label>
<input
v-model="form.password"
type="text"
required
>
</div>
<div>
<label>Confirm password</label>
<input
v-model="form.passwordConfirm"
type="text"
required
>
</div>
<div class="buttons">
<button type="submit">
Submit
</button>
</div>
</form>
</div>
<div
v-if="errorMessage"
class="banner-notice error"
>
<div>
<p>{{ errorMessage }}</p>
</div>
</div>
</div>
</template>
<style scoped>
@import "/assets/css/auth.css";
</style>