0, 'time' => time()]; } if (time() - $_SESSION[$rate_key]['time'] > 900) { $_SESSION[$rate_key] = ['count' => 0, 'time' => time()]; } $locked = $_SESSION[$rate_key]['count'] >= 5; /* ── Auto level promotion (December only) ─────────────────────── */ $currentMonth = (int)date("n"); $currentYear = (int)date("Y"); $r = $conn->query("SELECT value FROM settings WHERE name='last_promotion_year'"); if ($r && $row = $r->fetch_assoc()) { $lastPromotionYear = (int)$row['value']; } else { $conn->query("INSERT INTO settings (name, value) VALUES ('last_promotion_year','0')"); $lastPromotionYear = 0; } if ($currentMonth >= 12 && $currentYear > $lastPromotionYear) { $conn->query("UPDATE users SET level = level + 100 WHERE role = 'student' AND level < 400"); $ps = $conn->prepare("UPDATE settings SET value=? WHERE name='last_promotion_year'"); $ps->bind_param("s", $currentYear); $ps->execute(); } /* ── Handle POST ───────────────────────────────────────────────── */ if ($_SERVER['REQUEST_METHOD'] === 'POST') { if (empty($_POST['csrf_token']) || !hash_equals($_SESSION['csrf_token'], $_POST['csrf_token'])) { $error = "Invalid request. Please try again."; } elseif ($locked) { $error = "Too many failed attempts. Please wait 15 minutes and try again."; } else { $email = trim($_POST['email'] ?? ''); $password = $_POST['password'] ?? ''; if (empty($email) || empty($password)) { $error = "All fields are required."; } else { $stmt = $conn->prepare("SELECT id, fullname, email, password, role, level, is_verified FROM users WHERE email = ? LIMIT 1"); $stmt->bind_param("s", $email); $stmt->execute(); $result = $stmt->get_result(); if ($result->num_rows !== 1) { $error = "Invalid email or password."; $_SESSION[$rate_key]['count']++; } else { $user = $result->fetch_assoc(); if (!password_verify($password, $user['password'])) { $error = "Invalid email or password."; $_SESSION[$rate_key]['count']++; // Alert on 3+ failed attempts if ($_SESSION[$rate_key]['count'] >= 3) { $alertBody = "

" . $_SESSION[$rate_key]['count'] . " failed login attempts

" . "

Email tried: " . htmlspecialchars($email) . "

" . "

IP: {$ip}

" . "

Time: " . date('Y-m-d H:i:s') . "

"; sendMail('noreply@esesa-umat.com', "⚠️ Failed Login Attempts — ESESA Portal", $alertBody); } } elseif ((int)$user['is_verified'] === 0) { $error = "Your email is not verified. Please check your inbox."; } else { // ── SUCCESS ───────────────────────────────────── $_SESSION[$rate_key] = ['count' => 0, 'time' => time()]; if ($user['role'] === 'admin') { // Regenerate session then store 2FA data session_regenerate_id(true); $otp = str_pad(random_int(0, 999999), 6, '0', STR_PAD_LEFT); $expires = time() + 600; $_SESSION['2fa_pending'] = true; $_SESSION['2fa_otp'] = password_hash($otp, PASSWORD_DEFAULT); $_SESSION['2fa_expires'] = $expires; $_SESSION['2fa_name'] = $user['fullname']; $_SESSION['2fa_email'] = $user['email']; $_SESSION['2fa_ip'] = $ip; $_SESSION['2fa_agent'] = $_SERVER['HTTP_USER_AGENT'] ?? 'Unknown'; $otp_name = htmlspecialchars($user['fullname']); $otp_body = "

Admin Login Verification

Hi {$otp_name},

Your one-time login code is:

{$otp}

Expires in 10 minutes. Do not share with anyone.

If you did not request this, change your password immediately.

"; sendMail('noreply@esesa-umat.com', "Your ESESA Admin Login Code", $otp_body); header("Location: admin_2fa.php"); exit; } else { // Student login session_regenerate_id(true); $_SESSION['csrf_token'] = bin2hex(random_bytes(32)); $_SESSION['user'] = [ 'id' => (int)$user['id'], 'fullname' => $user['fullname'], 'email' => $user['email'], 'role' => $user['role'], 'level' => (int)$user['level'], ]; header("Location: portal_dashboard.php"); exit; } } } } } } ?> ESESA Portal Login

ESESA Portal

Please sign in to continue
Too many failed attempts. Please wait 15 minutes.