'login_restart']); } $user_id = (int)$_SESSION["login_user_id"]; // ── Step 2: Throttle resends ────────────────────────────────────────────────── if ((int)($_SESSION['otp_resends'] ?? 0) >= LOGIN_OTP_MAX_RESENDS) { rate_limit_reject(); } rate_limit_guard($pdo1, [ ['otp_resend_ip', rate_limit_client_ip(), 10, 900], ['otp_resend_user', (string)$user_id, 5, 900], ]); // ── Step 3: Fetch user record ───────────────────────────────────────────────── $sth = $pdo1->prepare("select * from user where user_id = :user_id limit 1;"); $sth->execute([":user_id" => $user_id]); $temp = $sth->fetch(PDO::FETCH_ASSOC); if (!$temp) { $_SESSION = []; login_fail(401, 'Your sign-in has expired. Please enter your username and password again.', ['code' => 'login_restart']); } $user_email = $temp["email"]; // ── Step 4: Generate fresh 6-digit TOTP + random reference ──────────────────── // 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. $otpTime = time(); $otp = login_generate_otp($temp["password"], $otpTime); $reference_number = login_random_reference(); // ── Step 5: Send OTP email ──────────────────────────────────────────────────── // Company SMTP of the user's default company when configured, otherwise the // system-level $SMTP from config.php. $smtp_config = $SMTP; $default_company = (int)($temp["default_company"] ?? 0); if ($default_company > 0) { $sth = $pdo1->prepare("SELECT * FROM company_smtp WHERE company_id = :cid LIMIT 1"); $sth->execute([":cid" => $default_company]); $smtp_row = $sth->fetch(PDO::FETCH_ASSOC); if (!empty($smtp_row)) { $smtp_config = $smtp_row; } } require "../../../assets/utils/module/mailer.php"; $mailer = new mailer(["pdo1" => $pdo1, "pdo2" => $pdo2]); $mailer->send_email([ "company_id" => $smtp_config === $SMTP ? 0 : $default_company, "smtp" => $smtp_config, "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 6: Write the new OTP state ─────────────────────────────────────────── // The pending-login keys (login_data, login_user_id, password_verified_at) stay // as they are; only the OTP state is replaced. $_SESSION["otp"] = $otp; $_SESSION["otpTime"] = $otpTime; // new timestamp — login_confirm.php uses this $_SESSION["reference"] = $reference_number; $_SESSION["user_email"] = $user_email; $_SESSION["otp_attempts"] = 0; $_SESSION["otp_resends"] = (int)($_SESSION["otp_resends"] ?? 0) + 1; // ── Step 7: Respond ─────────────────────────────────────────────────────────── $answer["success"] = 1; $answer["message"] = "Login Complete!"; exit(json_encode($answer));