From 9f55f89e7264f37ddd4e7a40f7d7d2d3c1e2d23a Mon Sep 17 00:00:00 2001 From: Miguel Ribeiro Date: Sat, 25 May 2024 23:36:22 +0200 Subject: [PATCH] feat: add reset password functionality --- .../cronjobs/sendresetpasswordemails.php | 81 +++++++ endpoints/cronjobs/sendverificationemails.php | 2 +- includes/i18n/de.php | 4 + includes/i18n/el.php | 4 + includes/i18n/en.php | 4 + includes/i18n/es.php | 4 + includes/i18n/fr.php | 4 + includes/i18n/it.php | 5 + includes/i18n/jp.php | 4 + includes/i18n/ko.php | 4 + includes/i18n/pl.php | 4 + includes/i18n/pt.php | 4 + includes/i18n/pt_br.php | 4 + includes/i18n/ru.php | 4 + includes/i18n/sr.php | 4 + includes/i18n/sr_lat.php | 4 + includes/i18n/tr.php | 4 + includes/i18n/zh_cn.php | 4 + includes/i18n/zh_tw.php | 4 + login.php | 16 +- passwordreset.php | 216 ++++++++++++++++++ styles/login.css | 11 + 22 files changed, 392 insertions(+), 3 deletions(-) create mode 100644 endpoints/cronjobs/sendresetpasswordemails.php create mode 100644 passwordreset.php diff --git a/endpoints/cronjobs/sendresetpasswordemails.php b/endpoints/cronjobs/sendresetpasswordemails.php new file mode 100644 index 000000000..4b8c83f53 --- /dev/null +++ b/endpoints/cronjobs/sendresetpasswordemails.php @@ -0,0 +1,81 @@ +prepare($query); + $result = $stmt->execute(); + $admin = $result->fetchArray(SQLITE3_ASSOC); + + $query = "SELECT * FROM password_resets WHERE email_sent = 0"; + $stmt = $db->prepare($query); + $result = $stmt->execute(); + + $rows = []; + while ($row = $result->fetchArray(SQLITE3_ASSOC)) { + $rows[] = $row; + } + + if ($rows) { + if ($admin['smtp_address'] && $admin['smtp_port'] && $admin['smtp_username'] && $admin['smtp_password'] && $admin['encryption']) { + // There are SMTP settings + $smtpAddress = $admin['smtp_address']; + $smtpPort = $admin['smtp_port']; + $smtpUsername = $admin['smtp_username']; + $smtpPassword = $admin['smtp_password']; + $fromEmail = empty($admin['from_email']) ? 'wallos@wallosapp.com' : $admin['from_email']; + $encryption = $admin['encryption']; + $server_url = $admin['server_url']; + + require __DIR__ . '/../../libs/PHPMailer/PHPMailer.php'; + require __DIR__ . '/../../libs/PHPMailer/SMTP.php'; + require __DIR__ . '/../../libs/PHPMailer/Exception.php'; + + $mail = new PHPMailer(true); + $mail->isSMTP(); + $mail->Host = $smtpAddress; + $mail->SMTPAuth = true; + $mail->Username = $smtpUsername; + $mail->Password = $smtpPassword; + $mail->SMTPSecure = $encryption; + $mail->Port = $smtpPort; + $mail->setFrom($fromEmail); + + try { + foreach ($rows as $user) { + $mail->addAddress($user['email']); + $mail->isHTML(true); + $mail->Subject = 'Wallos - Reset Password'; + $mail->Body = 'Logo +
+ A password reset was requested for your account. +
+ Please click the following link to reset your password: Reset Password'; + + $mail->send(); + + $query = "UPDATE password_resets SET email_sent = 1 WHERE id = :id"; + $stmt = $db->prepare($query); + $stmt->bindParam(':id', $user['id'], SQLITE3_INTEGER); + $stmt->execute(); + + $mail->clearAddresses(); + + echo "Verification email sent to " . $user['email'] . "
"; + } + } catch (Exception $e) { + echo "Message could not be sent. Mailer Error: {$mail->ErrorInfo}"; + } + } else { + // There are no SMTP settings + die("There are password reset emails to be sent but no SMTP settings found."); + } + } else { + // There are no verification emails to be sent + die("No verification emails to be sent."); + } + +?> \ No newline at end of file diff --git a/endpoints/cronjobs/sendverificationemails.php b/endpoints/cronjobs/sendverificationemails.php index a59db9f8f..d5954f22f 100644 --- a/endpoints/cronjobs/sendverificationemails.php +++ b/endpoints/cronjobs/sendverificationemails.php @@ -75,7 +75,7 @@ } } else { // There are no SMTP settings - die("There are verification email to be sent but no SMTP settings found."); + die("There are verification emails to be sent but no SMTP settings found."); } } else { // There are no verification emails to be sent diff --git a/includes/i18n/de.php b/includes/i18n/de.php index a13519509..42d78ab56 100644 --- a/includes/i18n/de.php +++ b/includes/i18n/de.php @@ -22,6 +22,10 @@ 'login_failed' => "Loginangaben sind nicht korrekt", 'registration_successful' => "Registrierung erfolgreich", 'user_email_waiting_verification' => "Ihre E-Mail muss noch verifiziert werden. Bitte überprüfen Sie Ihre E-Mail.", + // Password Reset Page + 'forgot_password' => "Passwort vergessen?", + 'reset_password' => "Passwort zurücksetzen", + 'reset_sent_check_email' => "Passwort zurücksetzen E-Mail wurde gesendet. Bitte überprüfen Sie Ihr Postfach.", // Header 'subscriptions' => "Abonnements", 'stats' => "Statistiken", diff --git a/includes/i18n/el.php b/includes/i18n/el.php index 0b7da423b..8ad4bdfc5 100644 --- a/includes/i18n/el.php +++ b/includes/i18n/el.php @@ -22,6 +22,10 @@ 'login_failed' => "Τα στοιχεία σύνδεσης είναι λανθασμένα", 'registration_successful' => "Επιτυχής Εγγραφή", 'user_email_waiting_verification' => "Το email σας πρέπει να επαληθευτεί. Παρακαλούμε ελέγξτε το email σας", + // Password Reset Page + 'forgot_password' => "Ξέχασες τον κωδικό σου; Κάνε κλικ", + 'reset_password' => "Επαναφορά κωδικού πρόσβασης", + 'reset_sent_check_email' => "Ένα email με οδηγίες για την επαναφορά του κωδικού πρόσβασης σας έχει σταλεί. Παρακαλώ ελέγξτε το email σας.", // Header 'subscriptions' => "Συνδρομές", 'stats' => "Στατιστικές", diff --git a/includes/i18n/en.php b/includes/i18n/en.php index e4bf3cca7..9c6e25e71 100644 --- a/includes/i18n/en.php +++ b/includes/i18n/en.php @@ -22,6 +22,10 @@ 'login_failed' => "Login details are incorrect", 'registration_successful' => "Registration successful", 'user_email_waiting_verification' => "Your email needs to be verified. Please check your email.", + // Password Reset Page + 'forgot_password' => "Forgot Password", + 'reset_password' => "Reset Password", + 'reset_sent_check_email' => "Reset email sent. Please check your email.", // Header 'subscriptions' => "Subscriptions", 'stats' => "Statistics", diff --git a/includes/i18n/es.php b/includes/i18n/es.php index 470f370b8..a7b9586a3 100644 --- a/includes/i18n/es.php +++ b/includes/i18n/es.php @@ -22,6 +22,10 @@ 'login_failed' => "Los detalles de inicio de sesión son incorrectos", 'registration_successful' => "Registro efectuado con éxito", 'user_email_waiting_verification' => "Tu correo electrónico necesita ser verificado. Por favor, compruebe su correo electrónico", + // Password Reset Page + 'forgot_password' => "¿Olvidaste tu contraseña?", + 'reset_password' => "Restablecer Contraseña", + 'reset_sent_check_email' => "Se ha enviado un correo electrónico con instrucciones para restablecer la contraseña. Por favor, compruebe su correo electrónico.", // Header 'subscriptions' => "Suscripciones", 'stats' => "Estadísticas", diff --git a/includes/i18n/fr.php b/includes/i18n/fr.php index d520a931e..33da15e5a 100644 --- a/includes/i18n/fr.php +++ b/includes/i18n/fr.php @@ -22,6 +22,10 @@ 'login_failed' => "Les détails de connexion sont incorrects", 'registration_successful' => "Inscription réussie", 'user_email_waiting_verification' => "Votre email doit être vérifié. Veuillez vérifier votre email", + // Password Reset Page + 'forgot_password' => "Mot de passe oublié", + 'reset_password' => "Réinitialiser le mot de passe", + 'reset_sent_check_email' => "Un courriel a été envoyé à l'adresse fournie. Vérifiez votre boîte de réception.", // En-tête 'subscriptions' => "Abonnements", 'stats' => "Statistiques", diff --git a/includes/i18n/it.php b/includes/i18n/it.php index e559cc7f6..40995037b 100644 --- a/includes/i18n/it.php +++ b/includes/i18n/it.php @@ -23,6 +23,11 @@ 'login_failed' => 'Le credenziali non sono corrette', 'registration_successful' => "L'account è stato creato con successo", 'user_email_waiting_verification' => "L'e-mail deve essere verificata. Controlla la tua email", + + // Password Reset Page + 'forgot_password' => "Hai dimenticato la password?", + 'reset_password' => "Reimposta password", + 'reset_sent_check_email' => "Un'email è stata inviata. Controlla la tua casella di posta", // Header 'subscriptions' => 'Abbonamenti', diff --git a/includes/i18n/jp.php b/includes/i18n/jp.php index 425b0eaa7..8bae89552 100644 --- a/includes/i18n/jp.php +++ b/includes/i18n/jp.php @@ -22,6 +22,10 @@ 'login_failed' => "ログイン情報が間違っています", 'registration_successful' => "登録に成功", 'user_email_waiting_verification' => "Eメールの確認が必要です。メールを確認してください。", + // Password Reset Page + 'forgot_password' => "パスワードを忘れた場合", + 'reset_password' => "パスワードをリセット", + 'reset_sent_check_email' => "パスワードリセットリンクが送信されました。メールを確認してください。", // Header 'subscriptions' => "定期購入", 'stats' => "統計", diff --git a/includes/i18n/ko.php b/includes/i18n/ko.php index 1a1f78626..263342fe1 100644 --- a/includes/i18n/ko.php +++ b/includes/i18n/ko.php @@ -22,6 +22,10 @@ 'login_failed' => "로그인 정보가 부정확합니다.", 'registration_successful' => "등록 성공", 'user_email_waiting_verification' => "이메일을 인증해야 합니다. 이메일을 확인해 주세요.", + // Password Reset Page + 'forgot_password' => "비밀번호를 잊으셨나요?", + 'reset_password' => "비밀번호 재설정", + 'reset_sent_check_email' => "비밀번호 재설정 이메일이 전송되었습니다. 이메일을 확인해 주세요.", // Header 'subscriptions' => "구독", 'stats' => "통계", diff --git a/includes/i18n/pl.php b/includes/i18n/pl.php index 94079028e..efc06f56e 100644 --- a/includes/i18n/pl.php +++ b/includes/i18n/pl.php @@ -22,6 +22,10 @@ 'login_failed' => "Dane logowania są nieprawidłowe", 'registration_successful' => "Pomyślnie zarejestrowano", 'user_email_waiting_verification' => "Twój adres e-mail musi zostać zweryfikowany. Sprawdź swój adres e-mail", + // Password Reset Page + 'forgot_password' => "Zapomniałeś hasła? Kliknij tutaj", + 'reset_password' => "Zresetuj hasło", + 'reset_sent_check_email' => "Link do zresetowania hasła został wysłany na Twój adres e-mail", // Header 'subscriptions' => "Subskrypcje", 'stats' => "Statystyki", diff --git a/includes/i18n/pt.php b/includes/i18n/pt.php index 860e380bc..7cd739548 100644 --- a/includes/i18n/pt.php +++ b/includes/i18n/pt.php @@ -22,6 +22,10 @@ 'login_failed' => "Dados de autenticação incorrectos", 'registration_successful' => "Registo efectuado com sucesso.", 'user_email_waiting_verification' => "O seu e-mail precisa de ser validado. Verifique o seu correio eletrónico", + // Password Reset Page + 'forgot_password' => "Esqueceu-se da password?", + 'reset_password' => "Repor Password", + 'reset_sent_check_email' => "Pedido de reposição de password enviado. Verifique o seu email.", // Header 'subscriptions' => "Subscrições", 'stats' => "Estatísticas", diff --git a/includes/i18n/pt_br.php b/includes/i18n/pt_br.php index f4a8abad5..6624ad521 100644 --- a/includes/i18n/pt_br.php +++ b/includes/i18n/pt_br.php @@ -22,6 +22,10 @@ 'login_failed' => "As informações de login estão incorretas", 'registration_successful' => "Registro bem-sucedido", 'user_email_waiting_verification' => "Seu e-mail precisa ser validado. Por favor, verifique seu e-mail", + // Password Reset Page + 'forgot_password' => "Esqueceu a senha?", + 'reset_password' => "Redefinir senha", + 'reset_sent_check_email' => "Redefinição de senha enviada. Por favor, verifique seu email", // Header 'subscriptions' => "Assinaturas", 'stats' => "Estatísticas", diff --git a/includes/i18n/ru.php b/includes/i18n/ru.php index 35f8017f9..c4029ec8f 100644 --- a/includes/i18n/ru.php +++ b/includes/i18n/ru.php @@ -22,6 +22,10 @@ 'login_failed' => "Данные для входа неверны", 'registration_successful' => "Регистрация прошла успешно", 'user_email_waiting_verification' => "Ваша электронная почта нуждается в проверке. Пожалуйста, проверьте свою электронную почту", + // Password Reset Page + 'forgot_password' => "Забыли пароль?", + 'reset_password' => "Сбросить пароль", + 'reset_sent_check_email' => "Ссылка для сброса пароля отправлена на вашу электронную почту", // Header 'subscriptions' => "Подписки", 'stats' => "Статистика", diff --git a/includes/i18n/sr.php b/includes/i18n/sr.php index 801f116ce..acab0e3f3 100644 --- a/includes/i18n/sr.php +++ b/includes/i18n/sr.php @@ -22,6 +22,10 @@ 'login_failed' => "Подаци за пријаву нису исправни", 'registration_successful' => "Пријава успешна", 'user_email_waiting_verification' => "Ваша е-пошта треба да буде верификована. Молимо прегледајте Е-пошту", + // Password Reset Page + 'forgot_password' => "Заборављена лозинка", + 'reset_password' => "Ресетуј лозинку", + 'reset_sent_check_email' => "Ресетовање лозинке је послато на вашу е-пошту", // Header 'subscriptions' => "Претплате", 'stats' => "Статистике", diff --git a/includes/i18n/sr_lat.php b/includes/i18n/sr_lat.php index 152ab395d..a96bf5713 100644 --- a/includes/i18n/sr_lat.php +++ b/includes/i18n/sr_lat.php @@ -22,6 +22,10 @@ 'login_failed' => "Podaci za prijavu nisu ispravni", 'registration_successful' => "Registracija uspešna", 'user_email_waiting_verification' => "Vaša e-pošta treba da bude verifikovana. Molimo pregledajte E-poštu", + // Password Reset Page + 'forgot_password' => "Zaboravili ste lozinku?", + 'reset_password' => "Resetuj lozinku", + 'reset_sent_check_email' => "Poslali smo vam e-poštu sa uputstvima za resetovanje lozinke", // Header 'subscriptions' => "Pretplate", 'stats' => "Statistike", diff --git a/includes/i18n/tr.php b/includes/i18n/tr.php index c06357e1a..bb240e387 100644 --- a/includes/i18n/tr.php +++ b/includes/i18n/tr.php @@ -22,6 +22,10 @@ 'login_failed' => "Giriş bilgileri hatalı", 'registration_successful' => "Kayıt başarılı", 'user_email_waiting_verification' => "E-postanızın doğrulanması gerekiyor. Lütfen e-postanızı kontrol edin", + // Password Reset Page + 'forgot_password' => "Şifremi Unuttum", + 'reset_password' => "Şifreyi Sıfırla", + 'reset_sent_check_email' => "Şifre sıfırlama bağlantısı e-posta adresinize gönderildi. Lütfen e-postanızı kontrol edin.", // Header 'subscriptions' => "Abonelikler", 'stats' => "İstatistikler", diff --git a/includes/i18n/zh_cn.php b/includes/i18n/zh_cn.php index 74a362b4b..499729260 100644 --- a/includes/i18n/zh_cn.php +++ b/includes/i18n/zh_cn.php @@ -23,6 +23,10 @@ 'login_failed' => "登录信息错误", 'registration_successful' => "注册成功", 'user_email_waiting_verification' => "您的电子邮件需要验证。请检查您的电子邮件", + // Password Reset Page + 'forgot_password' => "忘记密码", + 'reset_password' => "重置密码", + 'reset_sent_check_email' => "重置密码链接已发送到您的电子邮箱", // 页眉 'subscriptions' => "订阅", diff --git a/includes/i18n/zh_tw.php b/includes/i18n/zh_tw.php index 7fd561cb2..a1b7ff9e2 100644 --- a/includes/i18n/zh_tw.php +++ b/includes/i18n/zh_tw.php @@ -22,6 +22,10 @@ 'login_failed' => "登入資訊錯誤", 'registration_successful' => "註冊成功", 'user_email_waiting_verification' => "您的電子郵件需要驗證。 請查看你的信箱", + // Password Reset Page + 'forgot_password' => "忘記密碼", + 'reset_password' => "重設密碼", + 'reset_sent_check_email' => "重設密碼的電子郵件已發送,請檢查您的電子郵件", // 頁首 'subscriptions' => "訂閱", 'stats' => "統計", diff --git a/login.php b/login.php index 95ef64147..1b20e8de5 100644 --- a/login.php +++ b/login.php @@ -101,7 +101,7 @@ //Check if registration is open $registrations = false; -$adminQuery = "SELECT registrations_open, max_users FROM admin"; +$adminQuery = "SELECT registrations_open, max_users, server_url, smtp_address FROM admin"; $adminResult = $db->query($adminQuery); $adminRow = $adminResult->fetchArray(SQLITE3_ASSOC); $registrationsOpen = $adminRow['registrations_open']; @@ -119,6 +119,11 @@ } } +$resetPasswordEnabled = false; +if ($adminRow['smtp_address'] != "" && $adminRow['server_url'] != "") { + $resetPasswordEnabled = true; +} + ?> @@ -210,8 +215,15 @@ +
+ +
+ - diff --git a/passwordreset.php b/passwordreset.php new file mode 100644 index 000000000..968ad93a4 --- /dev/null +++ b/passwordreset.php @@ -0,0 +1,216 @@ +close(); + header("Location: ."); + exit(); +} + +$requestMode = true; +$resetMode = false; + +$theme = "light"; +if (isset($_COOKIE['theme'])) { + $theme = $_COOKIE['theme']; +} + +$colorTheme = "blue"; +if (isset($_COOKIE['colorTheme'])) { + $colorTheme = $_COOKIE['colorTheme']; +} + +$settings = $db->querySingle("SELECT * FROM admin", true); +if ($settings['smtp_address'] == "" || $settings['server_url'] == "") { + header("Location: ."); +} else { + $resetPasswordEnabled = true; +} + +$hasSuccessMessage = false; +$hasErrorMessage = false; +$passwordsMismatch = false; +$hideForm = false; + +if (isset($_POST['email']) && $_POST['email'] != "" && isset($_GET['submit']) && $_GET['submit']) { + $requestMode = true; + $resetMode = false; + $email = $_POST['email']; + $user = $db->querySingle("SELECT * FROM user WHERE email = '$email'", true); + if ($user) { + $db->exec("DELETE FROM password_resets WHERE email = '$email'"); + $token = bin2hex(random_bytes(32)); + $db->exec("INSERT INTO password_resets (user_id, email, token) VALUES (" . $user['id'] . ", '$email', '$token')"); + } + $hasSuccessMessage = true; +} + +if (isset($_GET['token']) && $_GET['token'] != "" && isset($_GET['email']) && $_GET['email'] != "") { + $requestMode = false; + $resetMode = true; + $token = $_GET['token']; + $matchCount = "SELECT COUNT(*) FROM password_resets WHERE token = :token and email = :email"; + $stmt = $db->prepare($matchCount); + $stmt->bindValue(':token', $token, SQLITE3_TEXT); + $stmt->bindValue(':email', $_GET['email'], SQLITE3_TEXT); + $count = $stmt->execute()->fetchArray(SQLITE3_NUM); + if ($count[0] == 0) { + $hasErrorMessage = true; + $hideForm = true; + } +} + +if (isset($_POST['password']) && $_POST['password'] != "" && isset($_POST['confirm_password']) && $_POST['confirm_password'] != "" && isset($_GET['submit']) && $_GET['submit']) { + $requestMode = false; + $resetMode = true; + $password = $_POST['password']; + $confirmPassword = $_POST['confirm_password']; + $token = $_POST['token']; + $email = $_POST['email']; + $resetQuery = "SELECT * FROM password_resets WHERE token = :token AND email = :email"; + $stmt = $db->prepare($resetQuery); + $stmt->bindValue(':token', $token, SQLITE3_TEXT); + $stmt->bindValue(':email', $email, SQLITE3_TEXT); + $reset = $stmt->execute()->fetchArray(SQLITE3_ASSOC); + + if ($reset) { + $user = $db->querySingle("SELECT * FROM user WHERE email = '" . $reset['email'] . "'", true); + if ($password == $confirmPassword) { + $passwordHash = password_hash($password, PASSWORD_DEFAULT); + $db->exec("UPDATE user SET password = '$passwordHash' WHERE id = " . $user['id']); + $db->exec("DELETE FROM password_resets WHERE token = '$token'"); + $hasSuccessMessage = true; + $hideForm = true; + } else { + $hasErrorMessage = true; + $passwordsMismatch = true; + } + } else { + $hasSuccessMessage = false; + $hasErrorMessage = true; + } +} + +?> + + + + + + "/> + Wallos - Subscription Tracker + + + + + + > + > + > + + + > + + +
+
+
+ Wallos Logo Wallos Logo +

+ +

+
+
+ +
+ + +
+
+ +
+ +
    +
  • +
+ +
    +
  • +
+ +
+ + + + +
+
+ + +
+
+ +
+ +
    +
  • +
+ +
    +
  • +
+ + +
+
+
+ + + + +?> \ No newline at end of file diff --git a/styles/login.css b/styles/login.css index 52a87865a..b73c9a136 100644 --- a/styles/login.css +++ b/styles/login.css @@ -190,6 +190,17 @@ input[type="checkbox"] { padding-top: 20px; } +.login-form-link { + text-align: center; + margin: 20px 0px; +} + +.login-form-link a { + color: var(--main-color); + text-decoration: none; + font-size: 16px; +} + /* TOAST MESSAGE */ .toast {