diff --git a/backend/controllers/authController.js b/backend/controllers/authController.js index 4fabb7af..d792ec5c 100644 --- a/backend/controllers/authController.js +++ b/backend/controllers/authController.js @@ -1,4 +1,4 @@ -import { register, activateUser, login, logout } from '../services/authService.js'; +import { register, activateUser, login, logout, requestPasswordReset, resetPassword } from '../services/authService.js'; const registerUser = async (req, res, next) => { try { @@ -45,4 +45,24 @@ const logoutUser = async (req, res, next) => { } }; -export { registerUser, activate, loginUser, logoutUser }; +const forgotPassword = async (req, res, next) => { + try { + const { email } = req.body; + const result = await requestPasswordReset(email); + res.status(200).json(result); + } catch (error) { + next(error); + } +}; + +const resetUserPassword = async (req, res, next) => { + try { + const { token, password } = req.body; + const result = await resetPassword(token, password); + res.status(200).json(result); + } catch (error) { + next(error); + } +}; + +export { registerUser, activate, loginUser, logoutUser, forgotPassword, resetUserPassword }; diff --git a/backend/migrations/20260130_add_password_reset_fields.sql b/backend/migrations/20260130_add_password_reset_fields.sql new file mode 100644 index 00000000..bc65b02b --- /dev/null +++ b/backend/migrations/20260130_add_password_reset_fields.sql @@ -0,0 +1,8 @@ +-- Felder für "Passwort vergessen"-Funktion +ALTER TABLE user +ADD COLUMN reset_token VARCHAR(255) NULL DEFAULT NULL +COMMENT 'Token für Passwort-Reset'; + +ALTER TABLE user +ADD COLUMN reset_token_expires DATETIME NULL DEFAULT NULL +COMMENT 'Ablaufzeitpunkt des Reset-Tokens'; diff --git a/backend/models/User.js b/backend/models/User.js index ba260072..10e0f262 100644 --- a/backend/models/User.js +++ b/backend/models/User.js @@ -37,6 +37,16 @@ const User = sequelize.define('User', { authCode: { type: DataTypes.STRING, allowNull: true + }, + resetToken: { + type: DataTypes.STRING, + allowNull: true, + comment: 'Token für Passwort-Reset' + }, + resetTokenExpires: { + type: DataTypes.DATE, + allowNull: true, + comment: 'Ablaufzeitpunkt des Reset-Tokens' } }, { underscored: true, diff --git a/backend/routes/authRoutes.js b/backend/routes/authRoutes.js index 6cbf8051..ea5612b3 100644 --- a/backend/routes/authRoutes.js +++ b/backend/routes/authRoutes.js @@ -1,11 +1,13 @@ import express from 'express'; -import { registerUser, activate, loginUser, logoutUser } from '../controllers/authController.js'; +import { registerUser, activate, loginUser, logoutUser, forgotPassword, resetUserPassword } from '../controllers/authController.js'; const router = express.Router(); router.post('/register', registerUser); router.get('/activate/:activationCode', activate); router.post('/login', loginUser); -router.post('/logout', logoutUser); // Ändere GET zu POST +router.post('/logout', logoutUser); +router.post('/forgot-password', forgotPassword); +router.post('/reset-password', resetUserPassword); export default router; diff --git a/backend/services/authService.js b/backend/services/authService.js index 59b3d236..e81c3377 100644 --- a/backend/services/authService.js +++ b/backend/services/authService.js @@ -2,7 +2,8 @@ import bcrypt from 'bcrypt'; import jwt from 'jsonwebtoken'; import User from '../models/User.js'; import UserToken from '../models/UserToken.js'; -import { sendActivationEmail } from './emailService.js'; +import crypto from 'crypto'; +import { sendActivationEmail, sendPasswordResetEmail } from './emailService.js'; import { devLog, errorLog } from '../utils/logger.js'; @@ -101,4 +102,80 @@ const logout = async (token) => { return { message: 'Logout erfolgreich' }; }; -export { register, activateUser, login, logout }; +const requestPasswordReset = async (email) => { + if (!email) { + const err = new Error('E-Mail-Adresse ist erforderlich.'); + err.status = 400; + throw err; + } + + const user = await User.findOne({ where: { email } }); + + // Aus Sicherheitsgründen IMMER Erfolg melden (verhindert E-Mail-Enumeration) + if (!user) { + return { message: 'Falls ein Konto mit dieser E-Mail existiert, wurde ein Link zum Zurücksetzen gesendet.' }; + } + + // Token erzeugen (kryptographisch sicher) + const resetToken = crypto.randomBytes(32).toString('hex'); + const resetTokenExpires = new Date(Date.now() + 60 * 60 * 1000); // 1 Stunde gültig + + user.resetToken = resetToken; + user.resetTokenExpires = resetTokenExpires; + await user.save(); + + try { + await sendPasswordResetEmail(email, resetToken); + } catch (mailError) { + errorLog('[authService.requestPasswordReset] Fehler beim E-Mail-Versand', { + message: mailError?.message, + code: mailError?.code, + }); + const err = new Error('E-Mail konnte nicht gesendet werden. Bitte Administrator kontaktieren.'); + err.status = 500; + throw err; + } + + return { message: 'Falls ein Konto mit dieser E-Mail existiert, wurde ein Link zum Zurücksetzen gesendet.' }; +}; + +const resetPassword = async (token, newPassword) => { + if (!token || !newPassword) { + const err = new Error('Token und neues Passwort sind erforderlich.'); + err.status = 400; + throw err; + } + + if (newPassword.length < 6) { + const err = new Error('Das Passwort muss mindestens 6 Zeichen lang sein.'); + err.status = 400; + throw err; + } + + const { Op } = await import('sequelize'); + const user = await User.findOne({ + where: { + resetToken: token, + resetTokenExpires: { [Op.gt]: new Date() } + } + }); + + if (!user) { + const err = new Error('Der Link ist ungültig oder abgelaufen. Bitte fordere einen neuen an.'); + err.status = 400; + throw err; + } + + // Neues Passwort setzen (wird automatisch durch beforeUpdate-Hook gehasht) + user.password = newPassword; + user.resetToken = null; + user.resetTokenExpires = null; + await user.save(); + + // Alle bestehenden Tokens des Users löschen (erzwingt Neuanmeldung) + await UserToken.destroy({ where: { userId: user.id } }); + + return { message: 'Passwort wurde erfolgreich geändert.' }; +}; + +export { register, activateUser, login, logout, requestPasswordReset, resetPassword }; diff --git a/backend/services/emailService.js b/backend/services/emailService.js index 793740c1..6da72e49 100644 --- a/backend/services/emailService.js +++ b/backend/services/emailService.js @@ -18,4 +18,36 @@ const sendActivationEmail = async (email, activationCode) => { await transporter.sendMail(mailOptions); }; -export { sendActivationEmail }; +const sendPasswordResetEmail = async (email, resetToken) => { + const resetLink = `${process.env.BASE_URL}/reset-password/${resetToken}`; + const mailOptions = { + from: process.env.EMAIL_USER, + to: email, + subject: 'Passwort zurücksetzen', + html: ` +
+

Passwort zurücksetzen

+

Du hast eine Anfrage zum Zurücksetzen deines Passworts gestellt.

+

Klicke auf den folgenden Link, um ein neues Passwort zu vergeben:

+

+ + Passwort zurücksetzen + +

+

+ Dieser Link ist 1 Stunde gültig.
+ Falls du diese Anfrage nicht gestellt hast, ignoriere diese E-Mail einfach. +

+
+

+ Falls der Button nicht funktioniert, kopiere diesen Link in deinen Browser:
+ ${resetLink} +

+
+ `, + }; + await transporter.sendMail(mailOptions); +}; + +export { sendActivationEmail, sendPasswordResetEmail }; diff --git a/frontend/src/i18n/locales/de-CH.json b/frontend/src/i18n/locales/de-CH.json index 561fd99c..6ef31aa6 100644 --- a/frontend/src/i18n/locales/de-CH.json +++ b/frontend/src/i18n/locales/de-CH.json @@ -65,7 +65,25 @@ "rememberMe": "Aagmeldet bliibe", "loginSuccess": "Erfolgriich aagmeldet", "logoutSuccess": "Erfolgriich abgmeldet", - "sessionExpired": "Dini Sitzig isch abglaufe. Du wirsch abgmeldet." + "sessionExpired": "Dini Sitzig isch abglaufe. Du wirsch abgmeldet.", + "noAccount": "No kei Konto?", + "toLogin": "Zum Login", + "loginFailed": "Login fehlgschlage. Bitte Zuegangsdaten prüefe.", + "activationFailed": "Aktivierig fehlgschlage. Bitte de Link überprüefe.", + "forgotPasswordDescription": "Gib dini E-Mail-Adrässe ii. Du bechunsch en Link, zum dis Passwort zruggsetze.", + "sendResetLink": "Link schicke", + "sending": "Wird gschickt...", + "resetEmailSent": "Falls es Konto mit dere E-Mail git, isch en Link zum Zruggsetze gschickt worde. Bitte prüef dis Postfach (au de Spam-Ordner).", + "resetRequestFailed": "Aafrag fehlgschlage. Bitte probiers nomal.", + "resetPassword": "Nöis Passwort vergee", + "newPassword": "Nöis Passwort", + "confirmPassword": "Passwort bestätige", + "saveNewPassword": "Passwort speichere", + "saving": "Wird gspeicheret...", + "passwordResetSuccess": "Dis Passwort isch erfolgriich gänderet worde. Du chasch dich jetzt aaloge.", + "passwordsDoNotMatch": "D'Passwörter stimed nöd überein.", + "passwordTooShort": "S'Passwort muess mindestens 6 Zeiche lang sii.", + "resetFailed": "Passwort het nöd chönne gänderet werde. De Link isch villicht abglaufe." }, "settings": { "title": "Iistellige", diff --git a/frontend/src/i18n/locales/de-extended.json b/frontend/src/i18n/locales/de-extended.json index e67a1dd2..68c33442 100644 --- a/frontend/src/i18n/locales/de-extended.json +++ b/frontend/src/i18n/locales/de-extended.json @@ -110,7 +110,24 @@ "loginFailed": "Login fehlgeschlagen. Bitte Zugangsdaten prüfen und erneut versuchen.", "registerSuccess": "Registrierung erfolgreich! Bitte prüfen Sie Ihre E-Mails, um den Account zu aktivieren.", "registerFailed": "Registrierung fehlgeschlagen. Bitte versuchen Sie es erneut.", - "activate": "Aktivieren" + "activate": "Aktivieren", + "activateAccount": "Account aktivieren", + "accountActivated": "Account aktiviert! Du kannst dich jetzt anmelden.", + "activationFailed": "Aktivierung fehlgeschlagen. Bitte überprüfe den Link oder versuche es erneut.", + "forgotPasswordDescription": "Gib deine E-Mail-Adresse ein. Du erhältst einen Link, um dein Passwort zurückzusetzen.", + "sendResetLink": "Link senden", + "sending": "Wird gesendet...", + "resetEmailSent": "Falls ein Konto mit dieser E-Mail existiert, wurde ein Link zum Zurücksetzen gesendet. Bitte prüfe dein Postfach (auch den Spam-Ordner).", + "resetRequestFailed": "Anfrage fehlgeschlagen. Bitte versuche es erneut.", + "resetPassword": "Neues Passwort vergeben", + "newPassword": "Neues Passwort", + "confirmPassword": "Passwort bestätigen", + "saveNewPassword": "Passwort speichern", + "saving": "Wird gespeichert...", + "passwordResetSuccess": "Dein Passwort wurde erfolgreich geändert. Du kannst dich jetzt mit deinem neuen Passwort anmelden.", + "passwordsDoNotMatch": "Die Passwörter stimmen nicht überein.", + "passwordTooShort": "Das Passwort muss mindestens 6 Zeichen lang sein.", + "resetFailed": "Passwort konnte nicht geändert werden. Der Link ist möglicherweise abgelaufen." }, "settings": { "title": "Einstellungen", diff --git a/frontend/src/i18n/locales/de.json b/frontend/src/i18n/locales/de.json index a786c216..0037d108 100644 --- a/frontend/src/i18n/locales/de.json +++ b/frontend/src/i18n/locales/de.json @@ -124,7 +124,21 @@ "activate": "Aktivieren", "activateAccount": "Account aktivieren", "accountActivated": "Account aktiviert! Du kannst dich jetzt anmelden.", - "activationFailed": "Aktivierung fehlgeschlagen. Bitte überprüfe den Link oder versuche es erneut." + "activationFailed": "Aktivierung fehlgeschlagen. Bitte überprüfe den Link oder versuche es erneut.", + "forgotPasswordDescription": "Gib deine E-Mail-Adresse ein. Du erhältst einen Link, um dein Passwort zurückzusetzen.", + "sendResetLink": "Link senden", + "sending": "Wird gesendet...", + "resetEmailSent": "Falls ein Konto mit dieser E-Mail existiert, wurde ein Link zum Zurücksetzen gesendet. Bitte prüfe dein Postfach (auch den Spam-Ordner).", + "resetRequestFailed": "Anfrage fehlgeschlagen. Bitte versuche es erneut.", + "resetPassword": "Neues Passwort vergeben", + "newPassword": "Neues Passwort", + "confirmPassword": "Passwort bestätigen", + "saveNewPassword": "Passwort speichern", + "saving": "Wird gespeichert...", + "passwordResetSuccess": "Dein Passwort wurde erfolgreich geändert. Du kannst dich jetzt mit deinem neuen Passwort anmelden.", + "passwordsDoNotMatch": "Die Passwörter stimmen nicht überein.", + "passwordTooShort": "Das Passwort muss mindestens 6 Zeichen lang sein.", + "resetFailed": "Passwort konnte nicht geändert werden. Der Link ist möglicherweise abgelaufen." }, "settings": { "title": "Einstellungen", diff --git a/frontend/src/i18n/locales/en-AU.json b/frontend/src/i18n/locales/en-AU.json index ecd3a656..11c0bc44 100644 --- a/frontend/src/i18n/locales/en-AU.json +++ b/frontend/src/i18n/locales/en-AU.json @@ -65,7 +65,25 @@ "rememberMe": "Remember me", "loginSuccess": "Successfully logged in", "logoutSuccess": "Successfully logged out", - "sessionExpired": "Your session has expired. You will be logged out." + "sessionExpired": "Your session has expired. You will be logged out.", + "noAccount": "Don't have an account?", + "toLogin": "Go to login", + "loginFailed": "Login failed. Please check your credentials and try again.", + "activationFailed": "Activation failed. Please check the link or try again.", + "forgotPasswordDescription": "Enter your email address. You will receive a link to reset your password.", + "sendResetLink": "Send link", + "sending": "Sending...", + "resetEmailSent": "If an account with this email exists, a reset link has been sent. Please check your inbox (and spam folder).", + "resetRequestFailed": "Request failed. Please try again.", + "resetPassword": "Set new password", + "newPassword": "New password", + "confirmPassword": "Confirm password", + "saveNewPassword": "Save password", + "saving": "Saving...", + "passwordResetSuccess": "Your password has been changed successfully. You can now log in with your new password.", + "passwordsDoNotMatch": "Passwords do not match.", + "passwordTooShort": "Password must be at least 6 characters long.", + "resetFailed": "Password could not be changed. The link may have expired." }, "settings": { "title": "Settings", diff --git a/frontend/src/i18n/locales/en-GB.json b/frontend/src/i18n/locales/en-GB.json index c1ba7641..67fc6c95 100644 --- a/frontend/src/i18n/locales/en-GB.json +++ b/frontend/src/i18n/locales/en-GB.json @@ -65,7 +65,31 @@ "rememberMe": "Remember me", "loginSuccess": "Successfully logged in", "logoutSuccess": "Successfully logged out", - "sessionExpired": "Your session has expired. You will be logged out." + "sessionExpired": "Your session has expired. You will be logged out.", + "noAccount": "Don't have an account?", + "hasAccount": "Already have an account?", + "toLogin": "Go to login", + "loginFailed": "Login failed. Please check your credentials and try again.", + "registerSuccess": "Registration successful! Please check your email to activate your account.", + "registerFailed": "Registration failed. Please try again.", + "activate": "Activate", + "activateAccount": "Activate account", + "accountActivated": "Account activated! You can now log in.", + "activationFailed": "Activation failed. Please check the link or try again.", + "forgotPasswordDescription": "Enter your email address. You will receive a link to reset your password.", + "sendResetLink": "Send link", + "sending": "Sending...", + "resetEmailSent": "If an account with this email exists, a reset link has been sent. Please check your inbox (and spam folder).", + "resetRequestFailed": "Request failed. Please try again.", + "resetPassword": "Set new password", + "newPassword": "New password", + "confirmPassword": "Confirm password", + "saveNewPassword": "Save password", + "saving": "Saving...", + "passwordResetSuccess": "Your password has been changed successfully. You can now log in with your new password.", + "passwordsDoNotMatch": "Passwords do not match.", + "passwordTooShort": "Password must be at least 6 characters long.", + "resetFailed": "Password could not be changed. The link may have expired." }, "settings": { "title": "Settings", diff --git a/frontend/src/i18n/locales/en-US.json b/frontend/src/i18n/locales/en-US.json index 7eed06b1..20639396 100644 --- a/frontend/src/i18n/locales/en-US.json +++ b/frontend/src/i18n/locales/en-US.json @@ -65,7 +65,25 @@ "rememberMe": "Remember me", "loginSuccess": "Successfully logged in", "logoutSuccess": "Successfully logged out", - "sessionExpired": "Your session has expired. You will be logged out." + "sessionExpired": "Your session has expired. You will be logged out.", + "noAccount": "Don't have an account?", + "toLogin": "Go to login", + "loginFailed": "Login failed. Please check your credentials and try again.", + "activationFailed": "Activation failed. Please check the link or try again.", + "forgotPasswordDescription": "Enter your email address. You will receive a link to reset your password.", + "sendResetLink": "Send link", + "sending": "Sending...", + "resetEmailSent": "If an account with this email exists, a reset link has been sent. Please check your inbox (and spam folder).", + "resetRequestFailed": "Request failed. Please try again.", + "resetPassword": "Set new password", + "newPassword": "New password", + "confirmPassword": "Confirm password", + "saveNewPassword": "Save password", + "saving": "Saving...", + "passwordResetSuccess": "Your password has been changed successfully. You can now log in with your new password.", + "passwordsDoNotMatch": "Passwords do not match.", + "passwordTooShort": "Password must be at least 6 characters long.", + "resetFailed": "Password could not be changed. The link may have expired." }, "settings": { "title": "Settings", diff --git a/frontend/src/i18n/locales/es.json b/frontend/src/i18n/locales/es.json index 4e2f77a2..be4a735c 100644 --- a/frontend/src/i18n/locales/es.json +++ b/frontend/src/i18n/locales/es.json @@ -65,7 +65,25 @@ "rememberMe": "Recordarme", "loginSuccess": "Sesión iniciada correctamente", "logoutSuccess": "Sesión cerrada correctamente", - "sessionExpired": "Tu sesión ha expirado. Serás desconectado." + "sessionExpired": "Tu sesión ha expirado. Serás desconectado.", + "noAccount": "¿No tienes cuenta?", + "toLogin": "Ir al inicio de sesión", + "loginFailed": "Inicio de sesión fallido. Por favor verifica tus credenciales.", + "activationFailed": "Activación fallida. Por favor verifica el enlace.", + "forgotPasswordDescription": "Introduce tu dirección de correo electrónico. Recibirás un enlace para restablecer tu contraseña.", + "sendResetLink": "Enviar enlace", + "sending": "Enviando...", + "resetEmailSent": "Si existe una cuenta con este correo, se ha enviado un enlace de restablecimiento. Revisa tu bandeja de entrada (y la carpeta de spam).", + "resetRequestFailed": "Solicitud fallida. Inténtalo de nuevo.", + "resetPassword": "Establecer nueva contraseña", + "newPassword": "Nueva contraseña", + "confirmPassword": "Confirmar contraseña", + "saveNewPassword": "Guardar contraseña", + "saving": "Guardando...", + "passwordResetSuccess": "Tu contraseña ha sido cambiada exitosamente. Ahora puedes iniciar sesión.", + "passwordsDoNotMatch": "Las contraseñas no coinciden.", + "passwordTooShort": "La contraseña debe tener al menos 6 caracteres.", + "resetFailed": "No se pudo cambiar la contraseña. El enlace puede haber expirado." }, "settings": { "title": "Configuración", diff --git a/frontend/src/i18n/locales/fil.json b/frontend/src/i18n/locales/fil.json index a32c2379..71f38cb5 100644 --- a/frontend/src/i18n/locales/fil.json +++ b/frontend/src/i18n/locales/fil.json @@ -65,7 +65,25 @@ "rememberMe": "Tandaan ako", "loginSuccess": "Matagumpay na nag-login", "logoutSuccess": "Matagumpay na nag-logout", - "sessionExpired": "Nag-expire na ang iyong session. Ikaw ay ma-logout." + "sessionExpired": "Nag-expire na ang iyong session. Ikaw ay ma-logout.", + "noAccount": "Wala pang account?", + "toLogin": "Pumunta sa login", + "loginFailed": "Nabigo ang pag-login. Suriin ang iyong credentials.", + "activationFailed": "Nabigo ang activation. Suriin ang link.", + "forgotPasswordDescription": "Ilagay ang iyong email address. Makakatanggap ka ng link para i-reset ang password.", + "sendResetLink": "Ipadala ang link", + "sending": "Ipinapadala...", + "resetEmailSent": "Kung may account sa email na ito, ipinadala na ang reset link. Suriin ang inbox (at spam folder).", + "resetRequestFailed": "Nabigo ang request. Subukan ulit.", + "resetPassword": "Magtakda ng bagong password", + "newPassword": "Bagong password", + "confirmPassword": "Kumpirmahin ang password", + "saveNewPassword": "I-save ang password", + "saving": "Sine-save...", + "passwordResetSuccess": "Matagumpay na nabago ang password. Maaari ka nang mag-login.", + "passwordsDoNotMatch": "Hindi magkatugma ang mga password.", + "passwordTooShort": "Ang password ay dapat hindi bababa sa 6 na character.", + "resetFailed": "Hindi nabago ang password. Maaaring nag-expire na ang link." }, "settings": { "title": "Mga setting", diff --git a/frontend/src/i18n/locales/fr.json b/frontend/src/i18n/locales/fr.json index 32240242..a5c189c8 100644 --- a/frontend/src/i18n/locales/fr.json +++ b/frontend/src/i18n/locales/fr.json @@ -65,7 +65,25 @@ "rememberMe": "Se souvenir de moi", "loginSuccess": "Connexion réussie", "logoutSuccess": "Déconnexion réussie", - "sessionExpired": "Votre session a expiré. Vous allez être déconnecté." + "sessionExpired": "Votre session a expiré. Vous allez être déconnecté.", + "noAccount": "Pas encore de compte ?", + "toLogin": "Vers la connexion", + "loginFailed": "Connexion échouée. Veuillez vérifier vos identifiants.", + "activationFailed": "Activation échouée. Veuillez vérifier le lien.", + "forgotPasswordDescription": "Entrez votre adresse e-mail. Vous recevrez un lien pour réinitialiser votre mot de passe.", + "sendResetLink": "Envoyer le lien", + "sending": "Envoi en cours...", + "resetEmailSent": "Si un compte avec cette adresse existe, un lien de réinitialisation a été envoyé. Vérifiez votre boîte de réception (et les spams).", + "resetRequestFailed": "Demande échouée. Veuillez réessayer.", + "resetPassword": "Définir un nouveau mot de passe", + "newPassword": "Nouveau mot de passe", + "confirmPassword": "Confirmer le mot de passe", + "saveNewPassword": "Enregistrer le mot de passe", + "saving": "Enregistrement...", + "passwordResetSuccess": "Votre mot de passe a été modifié avec succès. Vous pouvez maintenant vous connecter.", + "passwordsDoNotMatch": "Les mots de passe ne correspondent pas.", + "passwordTooShort": "Le mot de passe doit contenir au moins 6 caractères.", + "resetFailed": "Le mot de passe n'a pas pu être modifié. Le lien a peut-être expiré." }, "settings": { "title": "Paramètres", diff --git a/frontend/src/i18n/locales/it.json b/frontend/src/i18n/locales/it.json index 0d04f0a0..73f4053c 100644 --- a/frontend/src/i18n/locales/it.json +++ b/frontend/src/i18n/locales/it.json @@ -65,7 +65,25 @@ "rememberMe": "Ricordami", "loginSuccess": "Accesso effettuato con successo", "logoutSuccess": "Uscita effettuata con successo", - "sessionExpired": "La tua sessione è scaduta. Verrai disconnesso." + "sessionExpired": "La tua sessione è scaduta. Verrai disconnesso.", + "noAccount": "Non hai un account?", + "toLogin": "Vai al login", + "loginFailed": "Accesso fallito. Controlla le credenziali.", + "activationFailed": "Attivazione fallita. Controlla il link.", + "forgotPasswordDescription": "Inserisci il tuo indirizzo email. Riceverai un link per reimpostare la password.", + "sendResetLink": "Invia link", + "sending": "Invio in corso...", + "resetEmailSent": "Se esiste un account con questa email, è stato inviato un link di reimpostazione. Controlla la posta (e lo spam).", + "resetRequestFailed": "Richiesta fallita. Riprova.", + "resetPassword": "Imposta nuova password", + "newPassword": "Nuova password", + "confirmPassword": "Conferma password", + "saveNewPassword": "Salva password", + "saving": "Salvataggio...", + "passwordResetSuccess": "La password è stata cambiata con successo. Ora puoi accedere.", + "passwordsDoNotMatch": "Le password non corrispondono.", + "passwordTooShort": "La password deve contenere almeno 6 caratteri.", + "resetFailed": "La password non è stata modificata. Il link potrebbe essere scaduto." }, "settings": { "title": "Impostazioni", diff --git a/frontend/src/i18n/locales/ja.json b/frontend/src/i18n/locales/ja.json index 363083c7..b496994c 100644 --- a/frontend/src/i18n/locales/ja.json +++ b/frontend/src/i18n/locales/ja.json @@ -65,7 +65,25 @@ "rememberMe": "ログイン状態を保持", "loginSuccess": "ログインに成功しました", "logoutSuccess": "ログアウトに成功しました", - "sessionExpired": "セッションが期限切れです。ログアウトされます。" + "sessionExpired": "セッションが期限切れです。ログアウトされます。", + "noAccount": "アカウントをお持ちでないですか?", + "toLogin": "ログインへ", + "loginFailed": "ログインに失敗しました。認証情報を確認してください。", + "activationFailed": "アクティベーションに失敗しました。リンクを確認してください。", + "forgotPasswordDescription": "メールアドレスを入力してください。パスワードリセット用のリンクが送信されます。", + "sendResetLink": "リンクを送信", + "sending": "送信中...", + "resetEmailSent": "このメールアドレスのアカウントが存在する場合、リセットリンクが送信されました。受信トレイ(迷惑メールフォルダも)を確認してください。", + "resetRequestFailed": "リクエストに失敗しました。再試行してください。", + "resetPassword": "新しいパスワードを設定", + "newPassword": "新しいパスワード", + "confirmPassword": "パスワードを確認", + "saveNewPassword": "パスワードを保存", + "saving": "保存中...", + "passwordResetSuccess": "パスワードが正常に変更されました。新しいパスワードでログインできます。", + "passwordsDoNotMatch": "パスワードが一致しません。", + "passwordTooShort": "パスワードは6文字以上必要です。", + "resetFailed": "パスワードを変更できませんでした。リンクの有効期限が切れている可能性があります。" }, "settings": { "title": "設定", diff --git a/frontend/src/i18n/locales/pl.json b/frontend/src/i18n/locales/pl.json index 1502f895..92419274 100644 --- a/frontend/src/i18n/locales/pl.json +++ b/frontend/src/i18n/locales/pl.json @@ -65,7 +65,25 @@ "rememberMe": "Zapamiętaj mnie", "loginSuccess": "Pomyślnie zalogowano", "logoutSuccess": "Pomyślnie wylogowano", - "sessionExpired": "Twoja sesja wygasła. Zostaniesz wylogowany." + "sessionExpired": "Twoja sesja wygasła. Zostaniesz wylogowany.", + "noAccount": "Nie masz konta?", + "toLogin": "Do logowania", + "loginFailed": "Logowanie nie powiodło się. Sprawdź dane logowania.", + "activationFailed": "Aktywacja nie powiodła się. Sprawdź link.", + "forgotPasswordDescription": "Podaj swój adres e-mail. Otrzymasz link do zresetowania hasła.", + "sendResetLink": "Wyślij link", + "sending": "Wysyłanie...", + "resetEmailSent": "Jeśli konto z tym adresem istnieje, wysłano link do resetowania. Sprawdź skrzynkę (i spam).", + "resetRequestFailed": "Żądanie nie powiodło się. Spróbuj ponownie.", + "resetPassword": "Ustaw nowe hasło", + "newPassword": "Nowe hasło", + "confirmPassword": "Potwierdź hasło", + "saveNewPassword": "Zapisz hasło", + "saving": "Zapisywanie...", + "passwordResetSuccess": "Hasło zostało zmienione pomyślnie. Możesz się teraz zalogować.", + "passwordsDoNotMatch": "Hasła nie są zgodne.", + "passwordTooShort": "Hasło musi mieć co najmniej 6 znaków.", + "resetFailed": "Nie udało się zmienić hasła. Link mógł wygasnąć." }, "settings": { "title": "Ustawienia", diff --git a/frontend/src/i18n/locales/th.json b/frontend/src/i18n/locales/th.json index 52ccd426..1e14626d 100644 --- a/frontend/src/i18n/locales/th.json +++ b/frontend/src/i18n/locales/th.json @@ -65,7 +65,25 @@ "rememberMe": "จดจำฉัน", "loginSuccess": "เข้าสู่ระบบสำเร็จ", "logoutSuccess": "ออกจากระบบสำเร็จ", - "sessionExpired": "เซสชันของคุณหมดอายุแล้ว คุณจะถูกออกจากระบบ" + "sessionExpired": "เซสชันของคุณหมดอายุแล้ว คุณจะถูกออกจากระบบ", + "noAccount": "ยังไม่มีบัญชี?", + "toLogin": "ไปที่เข้าสู่ระบบ", + "loginFailed": "เข้าสู่ระบบล้มเหลว กรุณาตรวจสอบข้อมูลของคุณ", + "activationFailed": "การเปิดใช้งานล้มเหลว กรุณาตรวจสอบลิงก์", + "forgotPasswordDescription": "กรอกอีเมลของคุณ คุณจะได้รับลิงก์สำหรับรีเซ็ตรหัสผ่าน", + "sendResetLink": "ส่งลิงก์", + "sending": "กำลังส่ง...", + "resetEmailSent": "หากมีบัญชีที่ใช้อีเมลนี้ ลิงก์รีเซ็ตได้ถูกส่งแล้ว กรุณาตรวจสอบกล่องจดหมาย (และสแปม)", + "resetRequestFailed": "คำขอล้มเหลว กรุณาลองอีกครั้ง", + "resetPassword": "ตั้งรหัสผ่านใหม่", + "newPassword": "รหัสผ่านใหม่", + "confirmPassword": "ยืนยันรหัสผ่าน", + "saveNewPassword": "บันทึกรหัสผ่าน", + "saving": "กำลังบันทึก...", + "passwordResetSuccess": "เปลี่ยนรหัสผ่านสำเร็จ คุณสามารถเข้าสู่ระบบได้แล้ว", + "passwordsDoNotMatch": "รหัสผ่านไม่ตรงกัน", + "passwordTooShort": "รหัสผ่านต้องมีอย่างน้อย 6 ตัวอักษร", + "resetFailed": "ไม่สามารถเปลี่ยนรหัสผ่านได้ ลิงก์อาจหมดอายุแล้ว" }, "settings": { "title": "การตั้งค่า", diff --git a/frontend/src/i18n/locales/tl.json b/frontend/src/i18n/locales/tl.json index a32c2379..71f38cb5 100644 --- a/frontend/src/i18n/locales/tl.json +++ b/frontend/src/i18n/locales/tl.json @@ -65,7 +65,25 @@ "rememberMe": "Tandaan ako", "loginSuccess": "Matagumpay na nag-login", "logoutSuccess": "Matagumpay na nag-logout", - "sessionExpired": "Nag-expire na ang iyong session. Ikaw ay ma-logout." + "sessionExpired": "Nag-expire na ang iyong session. Ikaw ay ma-logout.", + "noAccount": "Wala pang account?", + "toLogin": "Pumunta sa login", + "loginFailed": "Nabigo ang pag-login. Suriin ang iyong credentials.", + "activationFailed": "Nabigo ang activation. Suriin ang link.", + "forgotPasswordDescription": "Ilagay ang iyong email address. Makakatanggap ka ng link para i-reset ang password.", + "sendResetLink": "Ipadala ang link", + "sending": "Ipinapadala...", + "resetEmailSent": "Kung may account sa email na ito, ipinadala na ang reset link. Suriin ang inbox (at spam folder).", + "resetRequestFailed": "Nabigo ang request. Subukan ulit.", + "resetPassword": "Magtakda ng bagong password", + "newPassword": "Bagong password", + "confirmPassword": "Kumpirmahin ang password", + "saveNewPassword": "I-save ang password", + "saving": "Sine-save...", + "passwordResetSuccess": "Matagumpay na nabago ang password. Maaari ka nang mag-login.", + "passwordsDoNotMatch": "Hindi magkatugma ang mga password.", + "passwordTooShort": "Ang password ay dapat hindi bababa sa 6 na character.", + "resetFailed": "Hindi nabago ang password. Maaaring nag-expire na ang link." }, "settings": { "title": "Mga setting", diff --git a/frontend/src/i18n/locales/zh.json b/frontend/src/i18n/locales/zh.json index d21cbf04..e46b146e 100644 --- a/frontend/src/i18n/locales/zh.json +++ b/frontend/src/i18n/locales/zh.json @@ -65,7 +65,25 @@ "rememberMe": "记住我", "loginSuccess": "登录成功", "logoutSuccess": "退出成功", - "sessionExpired": "您的会话已过期。您将被登出。" + "sessionExpired": "您的会话已过期。您将被登出。", + "noAccount": "还没有账号?", + "toLogin": "去登录", + "loginFailed": "登录失败。请检查您的凭据。", + "activationFailed": "激活失败。请检查链接。", + "forgotPasswordDescription": "输入您的电子邮件地址。您将收到重置密码的链接。", + "sendResetLink": "发送链接", + "sending": "发送中...", + "resetEmailSent": "如果该邮箱存在账号,已发送重置链接。请检查收件箱(和垃圾邮件文件夹)。", + "resetRequestFailed": "请求失败。请重试。", + "resetPassword": "设置新密码", + "newPassword": "新密码", + "confirmPassword": "确认密码", + "saveNewPassword": "保存密码", + "saving": "保存中...", + "passwordResetSuccess": "密码已成功更改。您现在可以使用新密码登录。", + "passwordsDoNotMatch": "密码不匹配。", + "passwordTooShort": "密码至少需要6个字符。", + "resetFailed": "无法更改密码。链接可能已过期。" }, "settings": { "title": "设置", diff --git a/frontend/src/router.js b/frontend/src/router.js index b12d570a..bb800a79 100644 --- a/frontend/src/router.js +++ b/frontend/src/router.js @@ -2,6 +2,8 @@ import { createRouter, createWebHistory } from 'vue-router'; import Register from './views/Register.vue'; import Login from './views/Login.vue'; import Activate from './views/Activate.vue'; +import ForgotPassword from './views/ForgotPassword.vue'; +import ResetPassword from './views/ResetPassword.vue'; import Home from './views/Home.vue'; import CreateClub from './views/CreateClub.vue'; import ClubView from './views/ClubView.vue'; @@ -26,6 +28,8 @@ const routes = [ { path: '/register', component: Register }, { path: '/login', component: Login }, { path: '/activate/:activationCode', component: Activate }, + { path: '/forgot-password', component: ForgotPassword }, + { path: '/reset-password/:token', component: ResetPassword }, { path: '/', component: Home }, { path: '/createclub', component: CreateClub }, { path: '/showclub/:clubId', component: ClubView }, diff --git a/frontend/src/views/ForgotPassword.vue b/frontend/src/views/ForgotPassword.vue new file mode 100644 index 00000000..898e36a0 --- /dev/null +++ b/frontend/src/views/ForgotPassword.vue @@ -0,0 +1,129 @@ + + + + + diff --git a/frontend/src/views/Login.vue b/frontend/src/views/Login.vue index 8e7c6460..cb785ae2 100644 --- a/frontend/src/views/Login.vue +++ b/frontend/src/views/Login.vue @@ -6,6 +6,9 @@ + diff --git a/frontend/src/views/ResetPassword.vue b/frontend/src/views/ResetPassword.vue new file mode 100644 index 00000000..f3c5e8dc --- /dev/null +++ b/frontend/src/views/ResetPassword.vue @@ -0,0 +1,144 @@ + + + + +