Implementiere Passwort-Zurücksetzen-Funktionalität im authController, einschließlich E-Mail-Versand und Token-Generierung. Aktualisiere die Benutzer- und Router-Modelle, um neue Routen für Passwort-Wiederherstellung hinzuzufügen. Passe die Frontend-Komponenten für die Passwort-Zurücksetzen-Logik an und verbessere die Benutzeroberfläche für die Eingabe der E-Mail-Adresse.

This commit is contained in:
Torsten Schulz (local)
2025-09-24 09:12:20 +02:00
parent 7c09abf534
commit 46783b35ea
15 changed files with 553 additions and 12 deletions

View File

@@ -25,7 +25,7 @@ axios.interceptors.response.use(
error => {
if (error.response && error.response.status === 401) {
store.dispatch('logout');
router.push('/');
router.push('/auth/login');
}
return Promise.reject(error);
}

View File

@@ -1,9 +1,9 @@
<template>
<div class="forgot-password">
<h2>Passwort vergessen</h2>
<form>
<form @submit.prevent="submitForgotPassword">
<label for="email">Email-Adresse:</label>
<input type="email" id="email" required>
<input type="email" id="email" v-model="email" required>
<button type="submit">Link zum Zurücksetzen senden</button>
</form>
@@ -13,12 +13,52 @@
<p>
<router-link to="/register">Registrieren</router-link>
</p>
<div v-if="dialogVisible" class="dialog">
<div class="dialog-content">
<h3>{{ dialogTitle }}</h3>
<p>{{ dialogMessage }}</p>
<button type="button" @click="closeDialog">Schließen</button>
</div>
</div>
</div>
</template>
<script>
import axios from '../../axios';
export default {
name: 'ForgotPassword'
name: 'ForgotPassword',
data() {
return {
email: '',
dialogTitle: '',
dialogMessage: '',
dialogVisible: false
};
},
methods: {
async submitForgotPassword() {
try {
const response = await axios.post('/auth/forgot-password', {
email: this.email
});
this.showDialog('E-Mail gesendet', response.data?.message || 'Ein Link zum Zurücksetzen wurde an Ihre E-Mail-Adresse gesendet.');
this.email = '';
} catch (err) {
const message = err?.response?.data?.message || err?.message || 'Ein unbekannter Fehler ist aufgetreten';
this.showDialog('Fehler', message);
}
},
showDialog(title, message) {
this.dialogTitle = title;
this.dialogMessage = message;
this.dialogVisible = true;
},
closeDialog() {
this.dialogVisible = false;
}
}
};
</script>
@@ -37,4 +77,22 @@
button {
margin-top: 20px;
}
.dialog {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
display: flex;
align-items: center;
justify-content: center;
background: rgba(0, 0, 0, 0.4);
}
.dialog-content {
background: #fff;
padding: 16px;
border-radius: 4px;
max-width: 420px;
width: 90%;
}
</style>

View File

@@ -0,0 +1,145 @@
<template>
<div class="reset-password">
<h2>Neues Passwort setzen</h2>
<form @submit.prevent="submitResetPassword">
<label for="password">Neues Passwort:</label>
<input type="password" id="password" v-model="password" required minlength="6">
<label for="confirmPassword">Passwort bestätigen:</label>
<input type="password" id="confirmPassword" v-model="confirmPassword" required minlength="6">
<button type="submit" :disabled="!isFormValid">Passwort zurücksetzen</button>
</form>
<p>
<router-link to="/login">Zurück zum Login</router-link>
</p>
<div v-if="dialogVisible" class="dialog">
<div class="dialog-content">
<h3>{{ dialogTitle }}</h3>
<p>{{ dialogMessage }}</p>
<button type="button" @click="closeDialog">Schließen</button>
</div>
</div>
</div>
</template>
<script>
import axios from '../../axios';
export default {
name: 'ResetPasswordComponent',
data() {
return {
password: '',
confirmPassword: '',
token: '',
dialogTitle: '',
dialogMessage: '',
dialogVisible: false
};
},
computed: {
isFormValid() {
return this.password.length >= 6 &&
this.password === this.confirmPassword &&
this.token;
}
},
mounted() {
// Token aus URL-Parameter extrahieren
const urlParams = new URLSearchParams(window.location.search);
this.token = urlParams.get('token');
if (!this.token) {
this.showDialog('Fehler', 'Ungültiger Reset-Link. Bitte fordern Sie einen neuen Link an.');
}
},
methods: {
async submitResetPassword() {
if (this.password !== this.confirmPassword) {
this.showDialog('Fehler', 'Die Passwörter stimmen nicht überein.');
return;
}
try {
const response = await axios.post('/auth/reset-password', {
token: this.token,
password: this.password
});
this.showDialog('Erfolg', response.data?.message || 'Passwort erfolgreich zurückgesetzt. Sie können sich jetzt anmelden.');
this.password = '';
this.confirmPassword = '';
// Nach 3 Sekunden zum Login weiterleiten
setTimeout(() => {
this.$router.push('/auth/login');
}, 3000);
} catch (err) {
const message = err?.response?.data?.message || err?.message || 'Ein unbekannter Fehler ist aufgetreten';
this.showDialog('Fehler', message);
}
},
showDialog(title, message) {
this.dialogTitle = title;
this.dialogMessage = message;
this.dialogVisible = true;
},
closeDialog() {
this.dialogVisible = false;
}
}
};
</script>
<style scoped>
.reset-password {
max-width: 400px;
margin: auto;
}
form {
display: flex;
flex-direction: column;
}
label {
margin-top: 10px;
}
input {
margin-top: 5px;
padding: 8px;
border: 1px solid #ddd;
border-radius: 4px;
}
button {
margin-top: 20px;
padding: 10px;
background-color: #007bff;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
button:disabled {
background-color: #ccc;
cursor: not-allowed;
}
.dialog {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
display: flex;
align-items: center;
justify-content: center;
background: rgba(0, 0, 0, 0.4);
}
.dialog-content {
background: #fff;
padding: 16px;
border-radius: 4px;
max-width: 420px;
width: 90%;
}
</style>

View File

@@ -52,6 +52,8 @@ router.beforeEach(async (to, from, next) => {
routes.forEach(route => router.addRoute(route));
addEditPagesRoute();
addRegisterRoute();
addForgotPasswordRoute();
addResetPasswordRoute();
router.addRoute({
path: '/:pathMatch(.*)*',
components: {
@@ -98,7 +100,37 @@ function addRegisterRoute() {
});
}
function addForgotPasswordRoute() {
if (router.hasRoute('/forgot-password')) {
router.removeRoute('/forgot-password');
}
router.addRoute({
path: '/forgot-password',
components: {
default: () => import('./content/authentication/ForgotPasswordContent.vue'),
rightColumn: loadComponent('ImageContent')
},
name: 'forgot-password'
});
}
function addResetPasswordRoute() {
if (router.hasRoute('/reset-password')) {
router.removeRoute('/reset-password');
}
router.addRoute({
path: '/reset-password',
components: {
default: () => import('./content/authentication/ResetPasswordContent.vue'),
rightColumn: loadComponent('ImageContent')
},
name: 'reset-password'
});
}
addEditPagesRoute();
addRegisterRoute();
addForgotPasswordRoute();
addResetPasswordRoute();
export default router;

View File

@@ -35,7 +35,7 @@ export default createStore({
localStorage.removeItem('isLoggedIn');
localStorage.removeItem('user');
localStorage.removeItem('token');
router.push('/');
router.push('/auth/login');
},
setMenuData(state, menuData) {
state.menuData = menuData;