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:
8
.env
8
.env
@@ -1 +1,7 @@
|
||||
VUE_APP_BACKEND_URL=http://localhost:3000/api
|
||||
SMTP_HOST=smtp.1blu.de
|
||||
SMTP_PORT=465
|
||||
SMTP_USER=e226079_0-kontakt
|
||||
SMTP_PASS=hitomisan
|
||||
SMTP_FROM=kontakt@tsschulz.de
|
||||
FRONTEND_URL=http://localhost:8080
|
||||
VUE_APP_BACKEND_URL=http://localhost:3002/api
|
||||
|
||||
79
config/email.js
Normal file
79
config/email.js
Normal file
@@ -0,0 +1,79 @@
|
||||
const nodemailer = require('nodemailer');
|
||||
|
||||
// E-Mail-Konfiguration
|
||||
const smtpConfig = {
|
||||
host: process.env.SMTP_HOST || 'smtp.1blu.de',
|
||||
port: process.env.SMTP_PORT || 465,
|
||||
secure: true, // true für 465, false für andere Ports
|
||||
auth: {
|
||||
user: process.env.SMTP_USER || 'e226079_0-kontakt',
|
||||
pass: process.env.SMTP_PASS || 'aNN31bll3Na!'
|
||||
}
|
||||
};
|
||||
|
||||
// Debug-Logging der SMTP-Konfiguration
|
||||
console.log('=== SMTP CONFIGURATION DEBUG ===');
|
||||
console.log('Host:', smtpConfig.host);
|
||||
console.log('Port:', smtpConfig.port);
|
||||
console.log('Secure:', smtpConfig.secure);
|
||||
console.log('User:', smtpConfig.auth.user);
|
||||
console.log('Pass:', smtpConfig.auth.pass.replace(/./g, '*')); // Passwort maskieren
|
||||
console.log('Environment Variables:');
|
||||
console.log(' SMTP_HOST:', process.env.SMTP_HOST || 'undefined');
|
||||
console.log(' SMTP_PORT:', process.env.SMTP_PORT || 'undefined');
|
||||
console.log(' SMTP_USER:', process.env.SMTP_USER || 'undefined');
|
||||
console.log(' SMTP_PASS:', process.env.SMTP_PASS ? '***' : 'undefined');
|
||||
console.log('================================');
|
||||
|
||||
const transporter = nodemailer.createTransport(smtpConfig);
|
||||
|
||||
// E-Mail-Template für Passwort-Reset
|
||||
const getPasswordResetEmailTemplate = (resetUrl, userName) => {
|
||||
return {
|
||||
subject: 'Passwort zurücksetzen - Miriam Gemeinde',
|
||||
html: `
|
||||
<div style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto;">
|
||||
<h2 style="color: #333;">Passwort zurücksetzen</h2>
|
||||
<p>Hallo ${userName},</p>
|
||||
<p>Sie haben eine Anfrage zum Zurücksetzen Ihres Passworts gestellt.</p>
|
||||
<p>Klicken Sie auf den folgenden Link, um ein neues Passwort zu erstellen:</p>
|
||||
<p style="margin: 20px 0;">
|
||||
<a href="${resetUrl}"
|
||||
style="background-color: #007bff; color: white; padding: 12px 24px; text-decoration: none; border-radius: 4px; display: inline-block;">
|
||||
Passwort zurücksetzen
|
||||
</a>
|
||||
</p>
|
||||
<p>Dieser Link ist 1 Stunde gültig.</p>
|
||||
<p>Falls Sie diese Anfrage nicht gestellt haben, können Sie diese E-Mail ignorieren.</p>
|
||||
<hr style="margin: 30px 0; border: none; border-top: 1px solid #eee;">
|
||||
<p style="color: #666; font-size: 12px;">
|
||||
Miriam Gemeinde<br>
|
||||
Diese E-Mail wurde automatisch generiert.
|
||||
</p>
|
||||
</div>
|
||||
`,
|
||||
text: `
|
||||
Passwort zurücksetzen - Miriam Gemeinde
|
||||
|
||||
Hallo ${userName},
|
||||
|
||||
Sie haben eine Anfrage zum Zurücksetzen Ihres Passworts gestellt.
|
||||
|
||||
Klicken Sie auf den folgenden Link, um ein neues Passwort zu erstellen:
|
||||
${resetUrl}
|
||||
|
||||
Dieser Link ist 1 Stunde gültig.
|
||||
|
||||
Falls Sie diese Anfrage nicht gestellt haben, können Sie diese E-Mail ignorieren.
|
||||
|
||||
---
|
||||
Miriam Gemeinde
|
||||
Diese E-Mail wurde automatisch generiert.
|
||||
`
|
||||
};
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
transporter,
|
||||
getPasswordResetEmailTemplate
|
||||
};
|
||||
@@ -1,7 +1,9 @@
|
||||
const bcrypt = require('bcryptjs');
|
||||
const { User } = require('../models');
|
||||
const { User, PasswordResetToken } = require('../models');
|
||||
const jwt = require('jsonwebtoken');
|
||||
const { addTokenToBlacklist } = require('../utils/blacklist');
|
||||
const { transporter, getPasswordResetEmailTemplate } = require('../config/email');
|
||||
const crypto = require('crypto');
|
||||
|
||||
function delay(ms) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
@@ -87,6 +89,106 @@ exports.login = async (req, res) => {
|
||||
}
|
||||
};
|
||||
|
||||
exports.forgotPassword = async (req, res) => {
|
||||
const { email } = req.body;
|
||||
if (!email) {
|
||||
return res.status(400).json({ message: 'E-Mail-Adresse ist erforderlich' });
|
||||
}
|
||||
try {
|
||||
const user = await User.findOne({ where: { email } });
|
||||
if (!user) {
|
||||
// Aus Sicherheitsgründen immer Erfolg melden, auch wenn E-Mail nicht existiert
|
||||
return res.status(200).json({ message: 'Falls die E-Mail-Adresse in unserem System registriert ist, erhalten Sie einen Link zum Zurücksetzen des Passworts.' });
|
||||
}
|
||||
|
||||
// Alte Reset-Tokens für diesen User löschen
|
||||
await PasswordResetToken.destroy({ where: { userId: user.id } });
|
||||
|
||||
// Neuen Reset-Token generieren
|
||||
const token = crypto.randomBytes(32).toString('hex');
|
||||
const expiresAt = new Date(Date.now() + 60 * 60 * 1000); // 1 Stunde
|
||||
|
||||
await PasswordResetToken.create({
|
||||
userId: user.id,
|
||||
token,
|
||||
expiresAt
|
||||
});
|
||||
|
||||
// Reset-URL generieren
|
||||
const resetUrl = `${process.env.FRONTEND_URL || 'http://localhost:8080'}/reset-password?token=${token}`;
|
||||
|
||||
// E-Mail versenden
|
||||
const emailTemplate = getPasswordResetEmailTemplate(resetUrl, user.name);
|
||||
|
||||
const mailOptions = {
|
||||
from: process.env.SMTP_FROM || 'noreply@miriamgemeinde.de',
|
||||
to: email,
|
||||
subject: emailTemplate.subject,
|
||||
html: emailTemplate.html,
|
||||
text: emailTemplate.text
|
||||
};
|
||||
|
||||
console.log('=== EMAIL SENDING DEBUG ===');
|
||||
console.log('From:', mailOptions.from);
|
||||
console.log('To:', mailOptions.to);
|
||||
console.log('Subject:', mailOptions.subject);
|
||||
console.log('Reset URL:', resetUrl);
|
||||
console.log('===========================');
|
||||
|
||||
await transporter.sendMail(mailOptions);
|
||||
|
||||
console.log('Password reset email sent to:', email);
|
||||
return res.status(200).json({ message: 'Falls die E-Mail-Adresse in unserem System registriert ist, erhalten Sie einen Link zum Zurücksetzen des Passworts.' });
|
||||
} catch (error) {
|
||||
console.error('Forgot password error:', error);
|
||||
return res.status(500).json({ message: 'Ein Fehler ist aufgetreten' });
|
||||
}
|
||||
};
|
||||
|
||||
exports.resetPassword = async (req, res) => {
|
||||
const { token, password } = req.body;
|
||||
if (!token || !password) {
|
||||
return res.status(400).json({ message: 'Token und neues Passwort sind erforderlich' });
|
||||
}
|
||||
if (password.length < 6) {
|
||||
return res.status(400).json({ message: 'Passwort muss mindestens 6 Zeichen lang sein' });
|
||||
}
|
||||
|
||||
try {
|
||||
// Token validieren
|
||||
const resetToken = await PasswordResetToken.findOne({
|
||||
where: {
|
||||
token,
|
||||
used: false,
|
||||
expiresAt: {
|
||||
[require('sequelize').Op.gt]: new Date()
|
||||
}
|
||||
},
|
||||
include: [{ model: User, as: 'user' }]
|
||||
});
|
||||
|
||||
if (!resetToken) {
|
||||
return res.status(400).json({ message: 'Ungültiger oder abgelaufener Token' });
|
||||
}
|
||||
|
||||
// Passwort hashen und aktualisieren
|
||||
const hashedPassword = await bcrypt.hash(password, 10);
|
||||
await User.update(
|
||||
{ password: hashedPassword },
|
||||
{ where: { id: resetToken.userId } }
|
||||
);
|
||||
|
||||
// Token als verwendet markieren
|
||||
await resetToken.update({ used: true });
|
||||
|
||||
console.log('Password reset successful for user:', resetToken.userId);
|
||||
return res.status(200).json({ message: 'Passwort erfolgreich zurückgesetzt' });
|
||||
} catch (error) {
|
||||
console.error('Reset password error:', error);
|
||||
return res.status(500).json({ message: 'Ein Fehler ist aufgetreten' });
|
||||
}
|
||||
};
|
||||
|
||||
exports.logout = async (req, res) => {
|
||||
const authHeader = req.header('Authorization');
|
||||
if (!authHeader) {
|
||||
|
||||
49
migrations/20250924062315-create-password-reset-tokens.js
Normal file
49
migrations/20250924062315-create-password-reset-tokens.js
Normal file
@@ -0,0 +1,49 @@
|
||||
'use strict';
|
||||
|
||||
/** @type {import('sequelize-cli').Migration} */
|
||||
module.exports = {
|
||||
async up (queryInterface, Sequelize) {
|
||||
await queryInterface.createTable('PasswordResetTokens', {
|
||||
id: {
|
||||
type: Sequelize.UUID,
|
||||
defaultValue: Sequelize.UUIDV4,
|
||||
primaryKey: true
|
||||
},
|
||||
userId: {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: false,
|
||||
references: {
|
||||
model: 'Users',
|
||||
key: 'id'
|
||||
},
|
||||
onUpdate: 'CASCADE',
|
||||
onDelete: 'CASCADE'
|
||||
},
|
||||
token: {
|
||||
type: Sequelize.STRING,
|
||||
allowNull: false,
|
||||
unique: true
|
||||
},
|
||||
expiresAt: {
|
||||
type: Sequelize.DATE,
|
||||
allowNull: false
|
||||
},
|
||||
used: {
|
||||
type: Sequelize.BOOLEAN,
|
||||
defaultValue: false
|
||||
},
|
||||
created_at: {
|
||||
type: Sequelize.DATE,
|
||||
allowNull: false
|
||||
},
|
||||
updated_at: {
|
||||
type: Sequelize.DATE,
|
||||
allowNull: false
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
async down (queryInterface, Sequelize) {
|
||||
await queryInterface.dropTable('PasswordResetTokens');
|
||||
}
|
||||
};
|
||||
45
models/PasswordResetToken.js
Normal file
45
models/PasswordResetToken.js
Normal file
@@ -0,0 +1,45 @@
|
||||
const { DataTypes } = require('sequelize');
|
||||
|
||||
module.exports = (sequelize) => {
|
||||
const PasswordResetToken = sequelize.define('PasswordResetToken', {
|
||||
id: {
|
||||
type: DataTypes.UUID,
|
||||
defaultValue: DataTypes.UUIDV4,
|
||||
primaryKey: true
|
||||
},
|
||||
userId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
references: {
|
||||
model: 'Users',
|
||||
key: 'id'
|
||||
}
|
||||
},
|
||||
token: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
unique: true
|
||||
},
|
||||
expiresAt: {
|
||||
type: DataTypes.DATE,
|
||||
allowNull: false
|
||||
},
|
||||
used: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
defaultValue: false
|
||||
}
|
||||
}, {
|
||||
timestamps: true,
|
||||
createdAt: 'created_at',
|
||||
updatedAt: 'updated_at'
|
||||
});
|
||||
|
||||
PasswordResetToken.associate = (models) => {
|
||||
PasswordResetToken.belongsTo(models.User, {
|
||||
foreignKey: 'userId',
|
||||
as: 'user'
|
||||
});
|
||||
};
|
||||
|
||||
return PasswordResetToken;
|
||||
};
|
||||
@@ -25,5 +25,12 @@ module.exports = (sequelize) => {
|
||||
updatedAt: false
|
||||
});
|
||||
|
||||
User.associate = (models) => {
|
||||
User.hasMany(models.PasswordResetToken, {
|
||||
foreignKey: 'userId',
|
||||
as: 'passwordResetTokens'
|
||||
});
|
||||
};
|
||||
|
||||
return User;
|
||||
};
|
||||
|
||||
19
package-lock.json
generated
19
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "miriamgemeinde",
|
||||
"version": "0.1.0",
|
||||
"version": "1.1.0",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "miriamgemeinde",
|
||||
"version": "0.1.0",
|
||||
"version": "1.1.0",
|
||||
"dependencies": {
|
||||
"@iconoir/vue": "^7.7.0",
|
||||
"@tiptap/extension-bold": "^2.4.0",
|
||||
@@ -40,6 +40,7 @@
|
||||
"moment": "^2.30.1",
|
||||
"multer": "^1.4.5-lts.1",
|
||||
"mysql2": "^3.10.1",
|
||||
"nodemailer": "^7.0.6",
|
||||
"nodemon": "^3.1.3",
|
||||
"sequelize": "^6.37.3",
|
||||
"sequelize-cli": "^6.6.2",
|
||||
@@ -12613,6 +12614,15 @@
|
||||
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz",
|
||||
"integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw=="
|
||||
},
|
||||
"node_modules/nodemailer": {
|
||||
"version": "7.0.6",
|
||||
"resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-7.0.6.tgz",
|
||||
"integrity": "sha512-F44uVzgwo49xboqbFgBGkRaiMgtoBrBEWCVincJPK9+S9Adkzt/wXCLKbf7dxucmxfTI5gHGB+bEmdyzN6QKjw==",
|
||||
"license": "MIT-0",
|
||||
"engines": {
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/nodemon": {
|
||||
"version": "3.1.4",
|
||||
"resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.4.tgz",
|
||||
@@ -27916,6 +27926,11 @@
|
||||
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz",
|
||||
"integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw=="
|
||||
},
|
||||
"nodemailer": {
|
||||
"version": "7.0.6",
|
||||
"resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-7.0.6.tgz",
|
||||
"integrity": "sha512-F44uVzgwo49xboqbFgBGkRaiMgtoBrBEWCVincJPK9+S9Adkzt/wXCLKbf7dxucmxfTI5gHGB+bEmdyzN6QKjw=="
|
||||
},
|
||||
"nodemon": {
|
||||
"version": "3.1.4",
|
||||
"resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.4.tgz",
|
||||
|
||||
@@ -41,6 +41,7 @@
|
||||
"moment": "^2.30.1",
|
||||
"multer": "^1.4.5-lts.1",
|
||||
"mysql2": "^3.10.1",
|
||||
"nodemailer": "^7.0.6",
|
||||
"nodemon": "^3.1.3",
|
||||
"sequelize": "^6.37.3",
|
||||
"sequelize-cli": "^6.6.2",
|
||||
|
||||
@@ -5,6 +5,8 @@ const authMiddleware = require('../middleware/authMiddleware');
|
||||
|
||||
router.post('/register', authController.register);
|
||||
router.post('/login', authController.login);
|
||||
router.post('/forgot-password', authController.forgotPassword);
|
||||
router.post('/reset-password', authController.resetPassword);
|
||||
router.post('/logout', authMiddleware, authController.logout);
|
||||
|
||||
module.exports = router;
|
||||
|
||||
@@ -19,7 +19,7 @@ const imageRouter = require('./routes/image');
|
||||
const filesRouter = require('./routes/files');
|
||||
|
||||
const app = express();
|
||||
const PORT = 3000;
|
||||
const PORT = 3002;
|
||||
|
||||
app.use(cors());
|
||||
app.use(bodyParser.json());
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
145
src/content/authentication/ResetPasswordContent.vue
Normal file
145
src/content/authentication/ResetPasswordContent.vue
Normal 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>
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user