Reset Passwort funktioniert
This commit is contained in:
@@ -10,6 +10,12 @@ func BaseRouterGrp(r *gin.Engine) *gin.RouterGroup {
|
||||
// Routes
|
||||
r.POST("/register", user.RegisterUser)
|
||||
r.POST("/login", user.LoginUser)
|
||||
|
||||
// Password Reset Routes (unprotected)
|
||||
r.POST("/password-reset/request", user.RequestPasswordReset)
|
||||
r.GET("/password-reset/validate/:token", user.ValidateResetToken)
|
||||
r.POST("/password-reset/reset", user.ResetPassword)
|
||||
|
||||
protected := r.Group("/api")
|
||||
protected.Use(user.AuthMiddleware())
|
||||
return protected
|
||||
|
||||
@@ -8,6 +8,7 @@ package user
|
||||
import (
|
||||
"bamort/logger"
|
||||
"crypto/md5"
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"net/http"
|
||||
@@ -199,3 +200,195 @@ func AuthMiddleware() gin.HandlerFunc {
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
// generateResetHash generiert einen sicheren Hash für Password-Reset
|
||||
func generateResetHash() (string, error) {
|
||||
bytes := make([]byte, 32)
|
||||
_, err := rand.Read(bytes)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return hex.EncodeToString(bytes), nil
|
||||
}
|
||||
|
||||
// sendResetEmail simuliert das Senden einer E-Mail (hier nur Logging)
|
||||
// In einer echten Implementierung würde hier ein E-Mail-Service verwendet
|
||||
func sendResetEmail(email, username, resetHash, frontendURL string) error {
|
||||
// Verwende die mitgegebene Frontend-URL oder fallback auf Standard
|
||||
baseURL := frontendURL
|
||||
if baseURL == "" {
|
||||
baseURL = "http://localhost:3000" // Fallback, sollte aber nicht verwendet werden
|
||||
}
|
||||
|
||||
resetLink := fmt.Sprintf("%s/reset-password?token=%s", baseURL, resetHash)
|
||||
|
||||
logger.Info("=== PASSWORD RESET EMAIL ===")
|
||||
logger.Info("An: %s", email)
|
||||
logger.Info("Betreff: Passwort zurücksetzen für %s", username)
|
||||
logger.Info("Nachricht:")
|
||||
logger.Info("Hallo %s,", username)
|
||||
logger.Info("")
|
||||
logger.Info("Sie haben eine Passwort-Zurücksetzung angefordert.")
|
||||
logger.Info("Klicken Sie auf den folgenden Link, um Ihr Passwort zurückzusetzen:")
|
||||
logger.Info("")
|
||||
logger.Info("%s", resetLink)
|
||||
logger.Info("")
|
||||
logger.Info("Dieser Link ist 14 Tage gültig.")
|
||||
logger.Info("Falls Sie diese Anfrage nicht gestellt haben, ignorieren Sie diese E-Mail.")
|
||||
logger.Info("")
|
||||
logger.Info("=== END EMAIL ===")
|
||||
|
||||
// TODO: Hier echte E-Mail-Integration hinzufügen
|
||||
// z.B. SendGrid, SMTP, etc.
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RequestPasswordReset Handler für Passwort-Reset-Anfrage
|
||||
func RequestPasswordReset(c *gin.Context) {
|
||||
logger.Debug("Starte Passwort-Reset-Anfrage...")
|
||||
|
||||
var input struct {
|
||||
Email string `json:"email" binding:"required,email"`
|
||||
RedirectURL string `json:"redirect_url,omitempty"` // Optionale Frontend-URL
|
||||
}
|
||||
|
||||
if err := c.ShouldBindJSON(&input); err != nil {
|
||||
logger.Error("Fehler beim Parsen der Reset-Anfrage: %s", err.Error())
|
||||
respondWithError(c, http.StatusBadRequest, "Gültige E-Mail-Adresse erforderlich")
|
||||
return
|
||||
}
|
||||
|
||||
// Frontend-URL aus Request verwenden
|
||||
redirectURL := input.RedirectURL
|
||||
if redirectURL == "" {
|
||||
// Fallback, sollte aber nicht verwendet werden, da Frontend die URL mitgeben sollte
|
||||
redirectURL = "http://localhost:3000"
|
||||
}
|
||||
|
||||
logger.Debug("Reset-Anfrage für E-Mail: %s", input.Email)
|
||||
|
||||
var user User
|
||||
if err := user.FindByEmail(input.Email); err != nil {
|
||||
// Aus Sicherheitsgründen keine Information preisgeben, ob die E-Mail existiert
|
||||
logger.Warn("Reset-Anfrage für nicht existierende E-Mail: %s", input.Email)
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"message": "Falls ein Account mit dieser E-Mail-Adresse existiert, wurde eine Reset-E-Mail gesendet.",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Generiere Reset-Hash
|
||||
resetHash, err := generateResetHash()
|
||||
if err != nil {
|
||||
logger.Error("Fehler beim Generieren des Reset-Hashes: %s", err.Error())
|
||||
respondWithError(c, http.StatusInternalServerError, "Fehler beim Verarbeiten der Anfrage")
|
||||
return
|
||||
}
|
||||
|
||||
// Speichere Reset-Hash in der Datenbank
|
||||
if err := user.SetPasswordResetHash(resetHash); err != nil {
|
||||
logger.Error("Fehler beim Speichern des Reset-Hashes für Benutzer %s: %s", user.Username, err.Error())
|
||||
respondWithError(c, http.StatusInternalServerError, "Fehler beim Verarbeiten der Anfrage")
|
||||
return
|
||||
}
|
||||
|
||||
// Sende Reset-E-Mail
|
||||
if err := sendResetEmail(user.Email, user.Username, resetHash, redirectURL); err != nil {
|
||||
logger.Error("Fehler beim Senden der Reset-E-Mail für Benutzer %s: %s", user.Username, err.Error())
|
||||
respondWithError(c, http.StatusInternalServerError, "Fehler beim Senden der E-Mail")
|
||||
return
|
||||
}
|
||||
|
||||
logger.Info("Reset-E-Mail erfolgreich für Benutzer %s (%s) gesendet", user.Username, user.Email)
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"message": "Falls ein Account mit dieser E-Mail-Adresse existiert, wurde eine Reset-E-Mail gesendet.",
|
||||
})
|
||||
}
|
||||
|
||||
// ResetPassword Handler für das Zurücksetzen des Passworts
|
||||
func ResetPassword(c *gin.Context) {
|
||||
logger.Debug("Starte Passwort-Reset...")
|
||||
|
||||
var input struct {
|
||||
Token string `json:"token" binding:"required"`
|
||||
NewPassword string `json:"new_password" binding:"required,min=6"`
|
||||
}
|
||||
|
||||
if err := c.ShouldBindJSON(&input); err != nil {
|
||||
logger.Error("Fehler beim Parsen der Reset-Daten: %s", err.Error())
|
||||
respondWithError(c, http.StatusBadRequest, "Token und neues Passwort (mind. 6 Zeichen) erforderlich")
|
||||
return
|
||||
}
|
||||
|
||||
logger.Debug("Reset-Versuch mit Token: %s", input.Token[:10]+"...")
|
||||
|
||||
var user User
|
||||
if err := user.FindByResetHash(input.Token); err != nil {
|
||||
logger.Warn("Ungültiger oder abgelaufener Reset-Token verwendet")
|
||||
respondWithError(c, http.StatusBadRequest, "Ungültiger oder abgelaufener Reset-Link")
|
||||
return
|
||||
}
|
||||
|
||||
// Zusätzliche Validierung des Tokens
|
||||
if !user.IsResetHashValid(input.Token) {
|
||||
logger.Warn("Reset-Token-Validierung fehlgeschlagen für Benutzer: %s", user.Username)
|
||||
respondWithError(c, http.StatusBadRequest, "Ungültiger oder abgelaufener Reset-Link")
|
||||
return
|
||||
}
|
||||
|
||||
// Neues Passwort hashen (gleiche Methode wie bei der Registrierung)
|
||||
hashedPassword := md5.Sum([]byte(input.NewPassword))
|
||||
user.PasswordHash = hex.EncodeToString(hashedPassword[:])
|
||||
|
||||
// Reset-Hash entfernen
|
||||
if err := user.ClearPasswordResetHash(); err != nil {
|
||||
logger.Error("Fehler beim Entfernen des Reset-Hashes für Benutzer %s: %s", user.Username, err.Error())
|
||||
respondWithError(c, http.StatusInternalServerError, "Fehler beim Aktualisieren des Accounts")
|
||||
return
|
||||
}
|
||||
|
||||
// Passwort speichern
|
||||
if err := user.Save(); err != nil {
|
||||
logger.Error("Fehler beim Speichern des neuen Passworts für Benutzer %s: %s", user.Username, err.Error())
|
||||
respondWithError(c, http.StatusInternalServerError, "Fehler beim Aktualisieren des Passworts")
|
||||
return
|
||||
}
|
||||
|
||||
logger.Info("Passwort erfolgreich zurückgesetzt für Benutzer: %s", user.Username)
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"message": "Passwort erfolgreich zurückgesetzt",
|
||||
})
|
||||
}
|
||||
|
||||
// ValidateResetToken Handler zur Validierung eines Reset-Tokens
|
||||
func ValidateResetToken(c *gin.Context) {
|
||||
logger.Debug("Validiere Reset-Token...")
|
||||
|
||||
token := c.Param("token")
|
||||
//token := c.Query("token")
|
||||
if token == "" {
|
||||
respondWithError(c, http.StatusBadRequest, "Token erforderlich")
|
||||
return
|
||||
}
|
||||
|
||||
var user User
|
||||
if err := user.FindByResetHash(token); err != nil {
|
||||
logger.Debug("Reset-Token nicht gefunden oder abgelaufen")
|
||||
respondWithError(c, http.StatusBadRequest, "Ungültiger oder abgelaufener Reset-Link")
|
||||
return
|
||||
}
|
||||
|
||||
if !user.IsResetHashValid(token) {
|
||||
logger.Debug("Reset-Token-Validierung fehlgeschlagen")
|
||||
respondWithError(c, http.StatusBadRequest, "Ungültiger oder abgelaufener Reset-Link")
|
||||
return
|
||||
}
|
||||
|
||||
logger.Debug("Reset-Token gültig für Benutzer: %s", user.Username)
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"valid": true,
|
||||
"username": user.Username,
|
||||
"expires": user.ResetPwHashExpires,
|
||||
})
|
||||
}
|
||||
|
||||
+43
-6
@@ -9,12 +9,14 @@ import (
|
||||
)
|
||||
|
||||
type User struct {
|
||||
UserID uint `gorm:"primaryKey" json:"id"`
|
||||
Username string `gorm:"unique" json:"username"`
|
||||
PasswordHash string `json:"password"`
|
||||
Email string `gorm:"unique" json:"email"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
UserID uint `gorm:"primaryKey" json:"id"`
|
||||
Username string `gorm:"unique" json:"username"`
|
||||
PasswordHash string `json:"password"`
|
||||
Email string `gorm:"unique" json:"email"`
|
||||
ResetPwHash *string `gorm:"index" json:"-"` // Hash für Password-Reset (wird nicht serialisiert)
|
||||
ResetPwHashExpires *time.Time `json:"-"` // Ablaufzeit für Password-Reset-Hash
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
func (object *User) Create() error {
|
||||
@@ -55,3 +57,38 @@ func (object *User) Save() error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// FindByEmail findet einen User anhand der E-Mail-Adresse
|
||||
func (object *User) FindByEmail(email string) error {
|
||||
err := database.DB.First(&object, "email = ?", email).Error
|
||||
return err
|
||||
}
|
||||
|
||||
// FindByResetHash findet einen User anhand des Reset-Hashes
|
||||
func (object *User) FindByResetHash(resetHash string) error {
|
||||
err := database.DB.First(&object, "reset_pw_hash = ? AND reset_pw_hash_expires > ?", resetHash, time.Now()).Error
|
||||
return err
|
||||
}
|
||||
|
||||
// SetPasswordResetHash setzt den Reset-Hash und die Ablaufzeit (14 Tage)
|
||||
func (object *User) SetPasswordResetHash(resetHash string) error {
|
||||
expiryTime := time.Now().Add(14 * 24 * time.Hour) // 14 Tage gültig
|
||||
object.ResetPwHash = &resetHash
|
||||
object.ResetPwHashExpires = &expiryTime
|
||||
return object.Save()
|
||||
}
|
||||
|
||||
// ClearPasswordResetHash entfernt den Reset-Hash
|
||||
func (object *User) ClearPasswordResetHash() error {
|
||||
object.ResetPwHash = nil
|
||||
object.ResetPwHashExpires = nil
|
||||
return object.Save()
|
||||
}
|
||||
|
||||
// IsResetHashValid prüft ob der Reset-Hash gültig und nicht abgelaufen ist
|
||||
func (object *User) IsResetHashValid(resetHash string) bool {
|
||||
if object.ResetPwHash == nil || object.ResetPwHashExpires == nil {
|
||||
return false
|
||||
}
|
||||
return *object.ResetPwHash == resetHash && time.Now().Before(*object.ResetPwHashExpires)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
<template>
|
||||
<div class="fullwidth-page" style="display: flex; justify-content: center; align-items: center; min-height: 100vh;">
|
||||
<div class="card" style="max-width: 400px; width: 100%; margin: 20px;">
|
||||
<div class="page-header">
|
||||
<h2>Passwort zurücksetzen</h2>
|
||||
<p style="color: #666; font-size: 0.9em; margin-top: 10px;">
|
||||
Geben Sie Ihre E-Mail-Adresse ein, um einen Reset-Link zu erhalten.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<form @submit.prevent="requestReset" v-if="!submitted">
|
||||
<div class="form-group">
|
||||
<label for="email">E-Mail-Adresse</label>
|
||||
<input
|
||||
v-model="email"
|
||||
type="email"
|
||||
id="email"
|
||||
name="email"
|
||||
class="form-control"
|
||||
placeholder="ihre@email.de"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
class="btn btn-primary"
|
||||
style="width: 100%; margin-top: 10px;"
|
||||
:disabled="isLoading"
|
||||
>
|
||||
<span v-if="isLoading">Wird gesendet...</span>
|
||||
<span v-else>Reset-Link senden</span>
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<div v-if="submitted" class="badge badge-success" style="width: 100%; margin-top: 15px; text-align: center; display: block;">
|
||||
<p style="margin: 10px 0;">
|
||||
<strong>E-Mail gesendet!</strong>
|
||||
</p>
|
||||
<p style="font-size: 0.9em; margin: 5px 0;">
|
||||
Falls ein Account mit dieser E-Mail-Adresse existiert, wurde ein Reset-Link gesendet.
|
||||
</p>
|
||||
<p style="font-size: 0.8em; margin: 5px 0; opacity: 0.8;">
|
||||
Prüfen Sie Ihre E-Mails und folgen Sie dem Link.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div v-if="error" class="badge badge-danger" style="width: 100%; margin-top: 15px; text-align: center; display: block;">
|
||||
{{ error }}
|
||||
</div>
|
||||
|
||||
<div style="text-align: center; margin-top: 20px; padding-top: 15px; border-top: 1px solid #dee2e6;">
|
||||
<router-link to="/" class="btn btn-secondary">
|
||||
Zurück zum Login
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import API from '../utils/api'
|
||||
|
||||
export default {
|
||||
name: 'ForgotPasswordForm',
|
||||
data() {
|
||||
return {
|
||||
email: '',
|
||||
error: '',
|
||||
submitted: false,
|
||||
isLoading: false,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async requestReset() {
|
||||
this.error = ''
|
||||
this.isLoading = true
|
||||
|
||||
try {
|
||||
await API.post('/password-reset/request', {
|
||||
email: this.email,
|
||||
redirect_url: window.location.origin, // Aktuelle Frontend-URL
|
||||
})
|
||||
|
||||
this.submitted = true
|
||||
console.log('Password reset email requested for:', this.email)
|
||||
} catch (err) {
|
||||
console.error('Password reset request error:', err)
|
||||
this.error = err.response?.data?.error || 'Fehler beim Senden der E-Mail. Versuchen Sie es später erneut.'
|
||||
} finally {
|
||||
this.isLoading = false
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* No custom CSS needed - using main.css classes */
|
||||
</style>
|
||||
@@ -41,6 +41,12 @@
|
||||
{{ error }}
|
||||
</div>
|
||||
|
||||
<div style="text-align: center; margin-top: 15px;">
|
||||
<router-link to="/forgot-password" style="color: #007bff; text-decoration: none; font-size: 0.9em;">
|
||||
Passwort vergessen?
|
||||
</router-link>
|
||||
</div>
|
||||
|
||||
<div style="text-align: center; margin-top: 20px; padding-top: 15px; border-top: 1px solid #dee2e6;">
|
||||
<p>Don't have an account? <router-link to="/register" class="btn btn-secondary">Register here</router-link></p>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,218 @@
|
||||
<template>
|
||||
<div class="fullwidth-page" style="display: flex; justify-content: center; align-items: center; min-height: 100vh;">
|
||||
<div class="card" style="max-width: 400px; width: 100%; margin: 20px;">
|
||||
|
||||
<!-- Loading State -->
|
||||
<div v-if="isValidating" class="page-header" style="text-align: center;">
|
||||
<h2>Validiere Reset-Link...</h2>
|
||||
<div style="margin-top: 20px;">
|
||||
<div class="spinner" style="margin: 0 auto;"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Invalid Token -->
|
||||
<div v-else-if="!isValidToken" class="page-header">
|
||||
<h2>Ungültiger Reset-Link</h2>
|
||||
<div class="badge badge-danger" style="width: 100%; margin-top: 15px; text-align: center; display: block;">
|
||||
<p style="margin: 10px 0;">
|
||||
Dieser Reset-Link ist ungültig oder abgelaufen.
|
||||
</p>
|
||||
<p style="font-size: 0.9em; margin: 5px 0;">
|
||||
Bitte fordern Sie einen neuen Reset-Link an.
|
||||
</p>
|
||||
</div>
|
||||
<div style="text-align: center; margin-top: 20px;">
|
||||
<router-link to="/forgot-password" class="btn btn-primary">
|
||||
Neuen Reset-Link anfordern
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Valid Token - Reset Form -->
|
||||
<div v-else>
|
||||
<div class="page-header">
|
||||
<h2>Neues Passwort setzen</h2>
|
||||
<p style="color: #666; font-size: 0.9em; margin-top: 10px;" v-if="userInfo.username">
|
||||
Für Benutzer: <strong>{{ userInfo.username }}</strong>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<form @submit.prevent="resetPassword" v-if="!resetSuccess">
|
||||
<div class="form-group">
|
||||
<label for="newPassword">Neues Passwort</label>
|
||||
<input
|
||||
v-model="newPassword"
|
||||
type="password"
|
||||
id="newPassword"
|
||||
name="newPassword"
|
||||
class="form-control"
|
||||
placeholder="Mindestens 6 Zeichen"
|
||||
required
|
||||
minlength="6"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="confirmPassword">Passwort bestätigen</label>
|
||||
<input
|
||||
v-model="confirmPassword"
|
||||
type="password"
|
||||
id="confirmPassword"
|
||||
name="confirmPassword"
|
||||
class="form-control"
|
||||
placeholder="Passwort wiederholen"
|
||||
required
|
||||
minlength="6"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
class="btn btn-primary"
|
||||
style="width: 100%; margin-top: 10px;"
|
||||
:disabled="isResetting || !passwordsMatch"
|
||||
>
|
||||
<span v-if="isResetting">Passwort wird gesetzt...</span>
|
||||
<span v-else>Passwort zurücksetzen</span>
|
||||
</button>
|
||||
|
||||
<div v-if="!passwordsMatch && confirmPassword" class="badge badge-warning" style="width: 100%; margin-top: 10px; text-align: center; display: block; font-size: 0.8em;">
|
||||
Die Passwörter stimmen nicht überein
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<!-- Success Message -->
|
||||
<div v-if="resetSuccess" class="badge badge-success" style="width: 100%; margin-top: 15px; text-align: center; display: block;">
|
||||
<p style="margin: 10px 0;">
|
||||
<strong>Passwort erfolgreich zurückgesetzt!</strong>
|
||||
</p>
|
||||
<p style="font-size: 0.9em; margin: 5px 0;">
|
||||
Sie können sich jetzt mit Ihrem neuen Passwort anmelden.
|
||||
</p>
|
||||
<div style="margin-top: 15px;">
|
||||
<router-link to="/" class="btn btn-primary">
|
||||
Zum Login
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="error" class="badge badge-danger" style="width: 100%; margin-top: 15px; text-align: center; display: block;">
|
||||
{{ error }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Back to Login (always visible) -->
|
||||
<div style="text-align: center; margin-top: 20px; padding-top: 15px; border-top: 1px solid #dee2e6;" v-if="!isValidating && !resetSuccess">
|
||||
<router-link to="/" class="btn btn-secondary">
|
||||
Zurück zum Login
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import API from '../utils/api'
|
||||
|
||||
export default {
|
||||
name: 'ResetPasswordForm',
|
||||
data() {
|
||||
return {
|
||||
token: '',
|
||||
newPassword: '',
|
||||
confirmPassword: '',
|
||||
error: '',
|
||||
isValidating: true,
|
||||
isValidToken: false,
|
||||
isResetting: false,
|
||||
resetSuccess: false,
|
||||
userInfo: {},
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
passwordsMatch() {
|
||||
return this.newPassword === this.confirmPassword
|
||||
}
|
||||
},
|
||||
async mounted() {
|
||||
// Get token from URL query parameter
|
||||
this.token = this.$route.query.token
|
||||
|
||||
if (!this.token) {
|
||||
this.isValidating = false
|
||||
this.isValidToken = false
|
||||
this.error = 'Kein Reset-Token gefunden'
|
||||
return
|
||||
}
|
||||
|
||||
await this.validateToken()
|
||||
},
|
||||
methods: {
|
||||
async validateToken() {
|
||||
try {
|
||||
const response = await API.get(`/password-reset/validate/${this.token}`)
|
||||
|
||||
this.isValidToken = response.data.valid
|
||||
this.userInfo = {
|
||||
username: response.data.username,
|
||||
expires: response.data.expires
|
||||
}
|
||||
|
||||
console.log('Token validation successful:', response.data)
|
||||
} catch (err) {
|
||||
console.error('Token validation error:', err)
|
||||
this.isValidToken = false
|
||||
this.error = 'Reset-Link ist ungültig oder abgelaufen'
|
||||
} finally {
|
||||
this.isValidating = false
|
||||
}
|
||||
},
|
||||
|
||||
async resetPassword() {
|
||||
if (!this.passwordsMatch) {
|
||||
this.error = 'Die Passwörter stimmen nicht überein'
|
||||
return
|
||||
}
|
||||
|
||||
if (this.newPassword.length < 6) {
|
||||
this.error = 'Das Passwort muss mindestens 6 Zeichen lang sein'
|
||||
return
|
||||
}
|
||||
|
||||
this.error = ''
|
||||
this.isResetting = true
|
||||
|
||||
try {
|
||||
await API.post('/password-reset/reset', {
|
||||
token: this.token,
|
||||
new_password: this.newPassword,
|
||||
})
|
||||
|
||||
this.resetSuccess = true
|
||||
console.log('Password reset successful')
|
||||
} catch (err) {
|
||||
console.error('Password reset error:', err)
|
||||
this.error = err.response?.data?.error || 'Fehler beim Zurücksetzen des Passworts. Versuchen Sie es erneut.'
|
||||
} finally {
|
||||
this.isResetting = false
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.spinner {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border: 2px solid #f3f3f3;
|
||||
border-top: 2px solid #007bff;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
</style>
|
||||
@@ -2,6 +2,8 @@ import { createRouter, createWebHistory } from "vue-router";
|
||||
import { isLoggedIn } from "../utils/auth"; // Import the helper function
|
||||
import LoginView from "../views/LoginView.vue";
|
||||
import RegisterView from "../views/RegisterView.vue";
|
||||
import ForgotPasswordView from "../views/ForgotPasswordView.vue";
|
||||
import ResetPasswordView from "../views/ResetPasswordView.vue";
|
||||
import DashboardView from "../views/DashboardView.vue";
|
||||
import AusruestungView from "../views/AusruestungView.vue";
|
||||
import MaintenanceView from "../views/MaintenanceView.vue";
|
||||
@@ -16,6 +18,8 @@ import CharacterCreation from "@/components/CharacterCreation.vue";
|
||||
const routes = [
|
||||
{ path: "/", name: "Login", component: LoginView },
|
||||
{ path: "/register", name: "Register", component: RegisterView },
|
||||
{ path: "/forgot-password", name: "ForgotPassword", component: ForgotPasswordView },
|
||||
{ path: "/reset-password", name: "ResetPassword", component: ResetPasswordView },
|
||||
{ path: "/dashboard", name: "Dashboard", component: DashboardView, meta: { requiresAuth: true } },
|
||||
{ path: "/ausruestung/:characterId", name: "Ausruestung", component: AusruestungView, meta: { requiresAuth: true } },
|
||||
{ path: "/maintenance", name: "Maintenance", component: MaintenanceView, meta: { requiresAuth: true } },
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
<template>
|
||||
<ForgotPasswordForm />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ForgotPasswordForm from '../components/ForgotPasswordForm.vue'
|
||||
|
||||
export default {
|
||||
name: 'ForgotPasswordView',
|
||||
components: {
|
||||
ForgotPasswordForm
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,14 @@
|
||||
<template>
|
||||
<ResetPasswordForm />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ResetPasswordForm from '../components/ResetPasswordForm.vue'
|
||||
|
||||
export default {
|
||||
name: 'ResetPasswordView',
|
||||
components: {
|
||||
ResetPasswordForm
|
||||
}
|
||||
}
|
||||
</script>
|
||||
Reference in New Issue
Block a user