added support for resetting a password while logged in
This commit is contained in:
parent
2ba24773d1
commit
199affd2d6
6 changed files with 126 additions and 12 deletions
|
|
@ -4,14 +4,14 @@ const props = defineProps<{
|
||||||
email: string
|
email: string
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const {resetPassword} = useAuth();
|
const {resetPasswordWithToken} = useAuth();
|
||||||
const password = ref("");
|
const password = ref("");
|
||||||
const passwordConfirmation = ref("");
|
const passwordConfirmation = ref("");
|
||||||
const tokenExpired = ref(false);
|
const tokenExpired = ref(false);
|
||||||
|
|
||||||
const handlePasswordReset = () => {
|
const handlePasswordReset = () => {
|
||||||
try {
|
try {
|
||||||
resetPassword(password.value, passwordConfirmation.value, props.token, props.email);
|
resetPasswordWithToken(password.value, passwordConfirmation.value, props.token, props.email);
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
if (error instanceof Error && error.message === "TOKEN_EXPIRED")
|
if (error instanceof Error && error.message === "TOKEN_EXPIRED")
|
||||||
tokenExpired.value = true;
|
tokenExpired.value = true;
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,49 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import ButtonAction from "~/components/common/button-action.vue";
|
||||||
|
|
||||||
|
const {resetPassword} = useAuth();
|
||||||
|
|
||||||
|
const currentPassword = ref("");
|
||||||
|
const newPassword = ref("");
|
||||||
|
const confirmNewPassword = ref("");
|
||||||
|
const errors = ref<string[]>([]);
|
||||||
|
const successMessage = ref("");
|
||||||
|
|
||||||
|
const handlePasswordReset = async () => {
|
||||||
|
errors.value = []
|
||||||
|
try {
|
||||||
|
await resetPassword(newPassword.value, confirmNewPassword.value, currentPassword.value);
|
||||||
|
successMessage.value = "Password reset successful!";
|
||||||
|
} catch (error) {
|
||||||
|
const fieldErrors = Object.values((error as any)?.errors ?? {}).flat() as string[]
|
||||||
|
errors.value = fieldErrors.length ? fieldErrors : [error?.message ?? "An error occurred. Please try again."]
|
||||||
|
successMessage.value = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<form class="password-form">
|
<form class="password-form" @submit.prevent>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="current-password">Current Password</label>
|
<label for="new-password">Current Password</label>
|
||||||
<input id="current-password" type="password"/>
|
<input id="new-password" v-model="currentPassword" type="password"/>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="current-password">New Password</label>
|
||||||
|
<input id="current-password" v-model="newPassword" type="password"/>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="current-password">New Password (again)</label>
|
||||||
|
<input id="current-password" v-model="confirmNewPassword" type="password"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="new-password">New Password</label>
|
<ButtonAction button-text="Reset Password" @action="handlePasswordReset"/>
|
||||||
<input id="new-password" type="password"/>
|
<ul v-if="errors.length" class="error-message">
|
||||||
</div>
|
<li v-for="msg in errors" :key="msg">{{ msg }}</li>
|
||||||
|
</ul>
|
||||||
|
<p v-if="successMessage" class="success-message">{{ successMessage }}</p>
|
||||||
</form>
|
</form>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
@ -22,4 +53,12 @@
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.error-message {
|
||||||
|
color: var(--color-error-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.success-message {
|
||||||
|
color: var(--color-button-primary);
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
@ -37,12 +37,32 @@ export const useAuth = () => {
|
||||||
await navigateTo('/auth/login')
|
await navigateTo('/auth/login')
|
||||||
}
|
}
|
||||||
|
|
||||||
const resetPassword = async (password: string, passwordConfirmation: string, token: string, email: string) => {
|
const resetPassword = async (password: string, passwordConfirmation: string, currentPassword: string) => {
|
||||||
await $fetch('/sanctum/csrf-cookie', {
|
await $fetch('/sanctum/csrf-cookie', {
|
||||||
baseURL: config.public.apiBase,
|
baseURL: config.public.apiBase,
|
||||||
credentials: 'include',
|
credentials: 'include',
|
||||||
})
|
})
|
||||||
await $api('/api/reset-password', {
|
await $api('/api/reset-password', {
|
||||||
|
method: 'POST',
|
||||||
|
body: {
|
||||||
|
password,
|
||||||
|
password_confirmation: passwordConfirmation,
|
||||||
|
current_password: currentPassword
|
||||||
|
},
|
||||||
|
onResponseError: ({response}) => {
|
||||||
|
const err = new Error(response._data?.message ?? 'Failed to reset password');
|
||||||
|
(err as any).errors = response._data?.errors ?? {}
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const resetPasswordWithToken = async (password: string, passwordConfirmation: string, token: string, email: string) => {
|
||||||
|
await $fetch('/sanctum/csrf-cookie', {
|
||||||
|
baseURL: config.public.apiBase,
|
||||||
|
credentials: 'include',
|
||||||
|
})
|
||||||
|
await $api('/api/reset-password-token', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: {
|
body: {
|
||||||
password,
|
password,
|
||||||
|
|
@ -59,6 +79,20 @@ export const useAuth = () => {
|
||||||
await navigateTo('/lists')
|
await navigateTo('/lists')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const forgotPassword = async (email: string) => {
|
||||||
|
await $fetch('/sanctum/csrf-cookie', {
|
||||||
|
baseURL: config.public.apiBase,
|
||||||
|
credentials: 'include',
|
||||||
|
})
|
||||||
|
await $api('/api/forgot-password', {
|
||||||
|
method: 'POST',
|
||||||
|
body: {
|
||||||
|
email
|
||||||
|
},
|
||||||
|
})
|
||||||
|
await navigateTo('/login')
|
||||||
|
}
|
||||||
|
|
||||||
const xsrfToken = useCookie('XSRF-TOKEN')
|
const xsrfToken = useCookie('XSRF-TOKEN')
|
||||||
|
|
||||||
const logout = async () => {
|
const logout = async () => {
|
||||||
|
|
@ -77,5 +111,5 @@ export const useAuth = () => {
|
||||||
navigateTo('/auth/login')
|
navigateTo('/auth/login')
|
||||||
}
|
}
|
||||||
|
|
||||||
return {login, register, resetPassword, logout}
|
return {login, register, forgotPassword, resetPassword, resetPasswordWithToken, logout}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,8 @@ export default defineNuxtRouteMiddleware((to) => {
|
||||||
const publicRoutes = [
|
const publicRoutes = [
|
||||||
'auth-login',
|
'auth-login',
|
||||||
'auth-register',
|
'auth-register',
|
||||||
'auth-reset-password',
|
'auth-reset-password-token',
|
||||||
|
'auth-forgot-password',
|
||||||
'invitations-token-accept',
|
'invitations-token-accept',
|
||||||
'invitations-token-decline',
|
'invitations-token-decline',
|
||||||
]
|
]
|
||||||
|
|
|
||||||
35
app/pages/auth/forgot-password.vue
Normal file
35
app/pages/auth/forgot-password.vue
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
|
||||||
|
import ButtonAction from "~/components/common/button-action.vue";
|
||||||
|
|
||||||
|
definePageMeta({
|
||||||
|
layout: 'auth'
|
||||||
|
})
|
||||||
|
|
||||||
|
const {forgotPassword} = useAuth();
|
||||||
|
const email = ref("");
|
||||||
|
const handlePasswordReset = () => {
|
||||||
|
forgotPassword(email.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<form class="forgot-password-form">
|
||||||
|
<label for="email">Email</label>
|
||||||
|
<input id="email" v-model="email" type="email"/>
|
||||||
|
<ButtonAction button-text="Send Reset Link" @action="handlePasswordReset"/>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
form {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
|
@ -10,11 +10,16 @@ definePageMeta({
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<h1>Log in</h1>
|
<h1>Log in</h1>
|
||||||
<login-form/>
|
<login-form/>
|
||||||
|
<NuxtLink class="link" to="/auth/forgot-password">Forgot Your Password?</NuxtLink>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
.link {
|
||||||
|
color: #007bff;
|
||||||
|
}
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue