prepare("select * from user where user_id = :user_id limit 1;"); $sth->execute([":user_id" => $user_id]); $temp = $sth->fetch(PDO::FETCH_ASSOC); $user_email = $temp["email"]; // ── Step 3–4: Re-verify password ───────────────────────────────────────────── // Safety check — ensures the session hasn't been tampered with between // login_otp.php and this resend call. if (password_verify(trim($data["password"]), $temp["password"])) { // ── Step 5a: Generate fresh 6-digit TOTP ────────────────────────────────── // Same HMAC-SHA1 algorithm as login_otp.php and login_confirm.php. // A new $otpTime is captured so the OTP window resets from this moment. function generateOTP($sercet_key, $time_step = 180, $length = 6) { global $otpTime; $otpTime = time(); // new timestamp — extends the 5-minute validity window $counter = floor($otpTime / $time_step); $data = pack("NN", 0, $counter); $hash = hash_hmac('sha1', $data, $sercet_key, true); $offset = ord(substr($hash, -1)) & 0x0F; $value = unpack("N", substr($hash, $offset, 4)); $otp = ($value[1] & 0x7FFFFFFF) % pow(10, $length); return str_pad(strval($otp), $length, '0', STR_PAD_LEFT); } // ── Step 5b: Generate 6-letter reference number ─────────────────────────── // Converts a second TOTP (derived from the first OTP as the key) to a // base-26 uppercase letter string shown on the OTP input screen. function numberToLetters($num) { $result = ''; while ($num > 0) { $mod = ($num - 1) % 26; $result = chr(65 + $mod) . $result; $num = intval(($num - $mod) / 26); } return str_pad($result, 6, 'A', STR_PAD_LEFT); } $otp = generateOTP($temp["password"]); $reference_number = numberToLetters(generateOTP($otp)); // ── Step 5c: Send OTP email ─────────────────────────────────────────────── // Uses the system-level $SMTP config from config.php. // The if(true) wrapper is a no-op placeholder from the original code — // the email block always executes. require "../../../assets/utils/module/mailer.php"; if (true) { $mailer = new mailer(["pdo1" => $pdo1]); $mailer->send_email([ "company_id" => 0, "smtp" => $SMTP, "subject" => "One Time Password (OTP) For reference number " . $reference_number, "message" => implode("\n", [ "Dear WMS user,", "", "You requested a One-Time Password (OTP) to log in to WMS.", "", "Please use the OTP below to complete your request:", "• OTP code: " . $otp, "• Reference number: " . $reference_number, "", "Please note:", "• This code will expire in 3 minutes. Please complete your action promptly.", "• Do not share this code with anyone to keep your account secure.", "• If you did not request this code, please ignore this email.", ]), "channel_name" => "WMS LOGIN OTP ", "to" => $user_email, "key" => $pinkey, ]); } // ── Step 5d: Reset session with new OTP state ───────────────────────────── // Full session is cleared before repopulating to avoid stale state // from the previous OTP attempt leaking into this one. $_SESSION = []; $_SESSION["login_data"] = $data; $_SESSION["otp"] = $otp; $_SESSION["otpTime"] = $otpTime; // new timestamp — login_confirm.php uses this $_SESSION["reference"] = $reference_number; $_SESSION["user_email"] = $user_email; $_SESSION["login_user_id"] = $user_id; // ── Step 6: Respond ─────────────────────────────────────────────────────── $answer["success"] = 1; $answer["message"] = "Login Complete!"; exit(json_encode($answer)); } else { // ── Password mismatch — clear cookies and reject ────────────────────────── $answer["message"] = "Incorrect Password"; setcookie("u", "", time() - 1, "/"); setcookie("h1", "", time() - 1, "/"); setcookie("h2", "", time() - 1, "/"); exit(json_encode($answer)); } $answer["success"] = 1; exit(json_encode($answer));