|
Server IP : 217.21.85.138 / Your IP : 216.73.216.103 Web Server : LiteSpeed System : Linux in-mum-web906.main-hosting.eu 4.18.0-553.37.1.lve.el8.x86_64 #1 SMP Mon Feb 10 22:45:17 UTC 2025 x86_64 User : u915722082 ( 915722082) PHP Version : 7.4.33 Disable Function : system, exec, shell_exec, passthru, mysql_list_dbs, ini_alter, dl, symlink, link, chgrp, leak, popen, apache_child_terminate, virtual, mb_send_mail MySQL : OFF | cURL : ON | WGET : ON | Perl : OFF | Python : OFF Directory (0755) : /home/u915722082/.nvm/../public_html/lohri/user/ |
| [ Home ] | [ C0mmand ] | [ Upload File ] |
|---|
<?php
// ajax_forgot.php
header('Content-Type: application/json; charset=utf-8');
// Enable error reporting for debugging (disable in production)
error_reporting(E_ALL);
ini_set('display_errors', 1);
ini_set('log_errors', 1);
session_start();
require_once __DIR__ . '/../config/config.php';
require_once __DIR__ . '/../lib/Mailer.php';
try {
$conn = getDatabaseConnection();
// Use UTC inside MySQL to avoid timezone drift
$conn->query("SET time_zone = '+00:00'");
// Safe JSON helper
function send_json($status, $message, $extra = []) {
echo json_encode(array_merge(['status' => $status, 'message' => $message], $extra));
exit;
}
// Check if request method is POST
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
send_json('error', 'Invalid request method');
}
// Inputs
$email = trim($_POST['email'] ?? '');
$otp = trim($_POST['otp'] ?? '');
// Validate email
if ($email === '' || !filter_var($email, FILTER_VALIDATE_EMAIL)) {
send_json('error', 'Invalid email address');
}
// STEP 1: Generate & send OTP
if ($otp === '') {
$q = $conn->prepare("SELECT uid, fname FROM tbl_user WHERE email = ? LIMIT 1");
if (!$q) {
error_log("Database prepare error: " . $conn->error);
send_json('error', 'Database error occurred');
}
$q->bind_param('s', $email);
$q->execute();
$result = $q->get_result();
$user = $result->fetch_assoc();
$q->close();
// Do not reveal if account exists for security
if (!$user) {
// Still send success message to prevent email enumeration
// But don't actually send email
error_log("Password reset attempted for non-existent email: $email");
send_json('ok', 'If this email exists, OTP has been sent', ['step' => 'otp_sent']);
}
// Generate 6-digit OTP with leading zeros
$genOtp = str_pad(strval(random_int(100000, 999999)), 6, '0', STR_PAD_LEFT);
// **FIX: Delete ALL old OTPs for this email (not just user_id)**
// This ensures no conflicts from previous attempts
$del = $conn->prepare("
DELETE pr FROM password_resets pr
JOIN tbl_user u ON pr.user_id = u.uid
WHERE u.email = ?
");
if (!$del) {
error_log("Database prepare error: " . $conn->error);
send_json('error', 'Database error occurred');
}
$del->bind_param('s', $email);
$del->execute();
$deletedRows = $del->affected_rows;
$del->close();
error_log("Deleted $deletedRows old OTP(s) for email: $email");
// Insert new OTP with proper UTC timestamp
$insSql = "
INSERT INTO password_resets (user_id, otp, otp_expires_at, used, created_at)
VALUES (?, ?, DATE_ADD(UTC_TIMESTAMP(), INTERVAL 10 MINUTE), 0, UTC_TIMESTAMP())
";
$ins = $conn->prepare($insSql);
if (!$ins) {
error_log("Database prepare error: " . $conn->error);
send_json('error', 'Database error occurred');
}
$ins->bind_param('is', $user['uid'], $genOtp);
$ok = $ins->execute();
if (!$ok) {
error_log("Insert OTP error: " . $ins->error);
$ins->close();
send_json('error', 'Failed to create OTP');
}
$insertId = $ins->insert_id;
$ins->close();
error_log("New OTP inserted with ID: $insertId for user: " . $user['uid']);
// Send OTP email
$emailSent = Mailer::sendOtp($email, $user['fname'] ?? 'User', $genOtp);
if (!$emailSent) {
error_log("Failed to send OTP email to: " . $email);
// **FIX: Delete the OTP if email fails to send**
$conn->query("DELETE FROM password_resets WHERE id = $insertId");
send_json('error', 'Failed to send OTP email. Please try again.');
}
// Log successful OTP generation (remove in production)
error_log("OTP generated and sent for user: " . $user['uid'] . ", email: $email, OTP: " . $genOtp);
send_json('ok', 'OTP sent to your email', ['step' => 'otp_sent']);
}
// STEP 2: Verify OTP
if ($otp !== '') {
// Validate OTP format - must be exactly 6 digits
if (!preg_match('/^\d{6}$/', $otp)) {
send_json('error', 'OTP must be exactly 6 digits');
}
// **FIX: More detailed query to debug issues**
$sel = $conn->prepare("
SELECT
pr.id,
pr.user_id,
pr.otp,
pr.otp_expires_at,
pr.used,
pr.created_at,
u.email,
u.fname,
CASE
WHEN pr.used = 1 THEN 'already_used'
WHEN pr.otp_expires_at <= UTC_TIMESTAMP() THEN 'expired'
ELSE 'valid'
END as otp_status,
TIMESTAMPDIFF(SECOND, UTC_TIMESTAMP(), pr.otp_expires_at) as seconds_remaining
FROM password_resets pr
JOIN tbl_user u ON pr.user_id = u.uid
WHERE u.email = ?
AND pr.otp = ?
ORDER BY pr.id DESC
LIMIT 1
");
if (!$sel) {
error_log("Database prepare error: " . $conn->error);
send_json('error', 'Database error occurred');
}
$sel->bind_param('ss', $email, $otp);
$sel->execute();
$result = $sel->get_result();
$row = $result->fetch_assoc();
$sel->close();
if (!$row) {
error_log("No OTP found for email: $email, OTP: $otp");
send_json('error', 'Invalid OTP');
}
// **FIX: Check OTP status and provide specific error messages**
if ($row['otp_status'] === 'already_used') {
error_log("OTP already used for email: $email, OTP: $otp");
send_json('error', 'This OTP has already been used. Please request a new one.');
}
if ($row['otp_status'] === 'expired') {
error_log("Expired OTP for email: $email, OTP: $otp, expired " . abs($row['seconds_remaining']) . " seconds ago");
send_json('error', 'OTP has expired. Please request a new one.');
}
if ($row['otp_status'] !== 'valid') {
error_log("Invalid OTP status for email: $email, status: " . $row['otp_status']);
send_json('error', 'Invalid or expired OTP');
}
// Mark OTP as used
$upd = $conn->prepare("UPDATE password_resets SET used = 1 WHERE id = ?");
if (!$upd) {
error_log("Database prepare error: " . $conn->error);
send_json('error', 'Database error occurred');
}
$upd->bind_param('i', $row['id']);
$updateSuccess = $upd->execute();
$upd->close();
if (!$updateSuccess) {
error_log("Failed to mark OTP as used for reset ID: " . $row['id']);
send_json('error', 'Failed to verify OTP');
}
// Store email in session for password reset page
$_SESSION['reset_email'] = $email;
$_SESSION['reset_verified'] = true;
$_SESSION['reset_time'] = time();
error_log("OTP verified successfully for email: $email, user: " . $row['user_id']);
send_json('ok', 'OTP verified successfully', ['step' => 'otp_verified']);
}
} catch (Exception $e) {
error_log("Exception in ajax_forgot.php: " . $e->getMessage() . " | " . $e->getTraceAsString());
send_json('error', 'An unexpected error occurred. Please try again.');
} catch (Error $e) {
error_log("Fatal error in ajax_forgot.php: " . $e->getMessage() . " | " . $e->getTraceAsString());
send_json('error', 'A system error occurred. Please try again.');
}
?>