I'm in a rush to release so I am adding features that are needed to make it usable.
This commit is contained in:
parent
c76ab1abf3
commit
4c2857b445
25 changed files with 2475 additions and 3475 deletions
110
classes/User.php
110
classes/User.php
|
@ -61,8 +61,116 @@ Class User{
|
|||
return $user;
|
||||
}
|
||||
|
||||
public function login(mixed $username, mixed $password)
|
||||
public function login($email, $password)
|
||||
{
|
||||
// Retrieve user record by email
|
||||
$stmt = $this->db->prepare("SELECT * FROM users WHERE email = ?");
|
||||
$stmt->bind_param("s", $email);
|
||||
$stmt->execute();
|
||||
$result = $stmt->get_result();
|
||||
$user_data = $result->fetch_assoc();
|
||||
$stmt->close();
|
||||
|
||||
// Check login_attempts table for lockout status
|
||||
$stmt = $this->db->prepare("SELECT * FROM login_attempts WHERE email = ?");
|
||||
$stmt->bind_param("s", $email);
|
||||
$stmt->execute();
|
||||
$attempt_data = $stmt->get_result()->fetch_assoc();
|
||||
$stmt->close();
|
||||
|
||||
$current_time = new \DateTime();
|
||||
|
||||
if ($attempt_data && !empty($attempt_data['lockout_until'])) {
|
||||
$lockout_until = new \DateTime($attempt_data['lockout_until']);
|
||||
if ($current_time < $lockout_until) {
|
||||
return "Account locked until " . $lockout_until->format('Y-m-d H:i:s') . ". Please try again later.";
|
||||
}
|
||||
}
|
||||
|
||||
// If no user record found, still update login_attempts to mitigate enumeration issues
|
||||
if (!$user_data) {
|
||||
$this->updateFailedAttempt($email);
|
||||
return "Invalid email or password.";
|
||||
}
|
||||
|
||||
// Verify the password using password_verify
|
||||
if (password_verify($password, $user_data['password'])) {
|
||||
// Successful login – clear login attempts and set session variables
|
||||
$this->resetLoginAttempts($email);
|
||||
$_SESSION['user'] = [
|
||||
'id' => $user_data['id'],
|
||||
'email' => $user_data['email'],
|
||||
'username' => $user_data['username'],
|
||||
'role' => $user_data['isAdmin'] ? 'admin' : 'user'
|
||||
];
|
||||
return true;
|
||||
} else {
|
||||
$attempts = $this->updateFailedAttempt($email);
|
||||
return "Invalid email or password. Attempt $attempts of 3.";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update (or create) a record in the login_attempts table for a failed attempt.
|
||||
* If attempts reach 3, set a lockout that doubles each time.
|
||||
* Returns the current number of attempts.
|
||||
*/
|
||||
private function updateFailedAttempt($email)
|
||||
{
|
||||
// Check for an existing record
|
||||
$stmt = $this->db->prepare("SELECT * FROM login_attempts WHERE email = ?");
|
||||
$stmt->bind_param("s", $email);
|
||||
$stmt->execute();
|
||||
$record = $stmt->get_result()->fetch_assoc();
|
||||
$stmt->close();
|
||||
|
||||
$current_time = new \DateTime();
|
||||
|
||||
if ($record) {
|
||||
$attempts = $record['attempts'] + 1;
|
||||
$lockouts = $record['lockouts'];
|
||||
if ($attempts >= 3) {
|
||||
// Increment lockouts and calculate the new lockout duration:
|
||||
// Duration in minutes = 30 * 2^(lockouts)
|
||||
$lockouts++;
|
||||
$duration = 30 * pow(2, $lockouts - 1);
|
||||
$lockout_until = clone $current_time;
|
||||
$lockout_until->modify("+{$duration} minutes");
|
||||
// Reset attempts to 0 on lockout
|
||||
$attempts = 0;
|
||||
$stmt = $this->db->prepare("UPDATE login_attempts SET attempts = ?, lockouts = ?, last_attempt = NOW(), lockout_until = ? WHERE email = ?");
|
||||
$lockout_until_str = $lockout_until->format('Y-m-d H:i:s');
|
||||
$stmt->bind_param("iiss", $attempts, $lockouts, $lockout_until_str, $email);
|
||||
$stmt->execute();
|
||||
$stmt->close();
|
||||
} else {
|
||||
$stmt = $this->db->prepare("UPDATE login_attempts SET attempts = ?, last_attempt = NOW() WHERE email = ?");
|
||||
$stmt->bind_param("is", $attempts, $email);
|
||||
$stmt->execute();
|
||||
$stmt->close();
|
||||
}
|
||||
return $attempts;
|
||||
} else {
|
||||
// Create a new record for this email
|
||||
$attempts = 1;
|
||||
$lockouts = 0;
|
||||
$stmt = $this->db->prepare("INSERT INTO login_attempts (email, attempts, lockouts, last_attempt) VALUES (?, ?, ?, NOW())");
|
||||
$stmt->bind_param("sii", $email, $attempts, $lockouts);
|
||||
$stmt->execute();
|
||||
$stmt->close();
|
||||
return $attempts;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the login_attempts record for the given email.
|
||||
*/
|
||||
private function resetLoginAttempts($email)
|
||||
{
|
||||
$stmt = $this->db->prepare("DELETE FROM login_attempts WHERE email = ?");
|
||||
$stmt->bind_param("s", $email);
|
||||
$stmt->execute();
|
||||
$stmt->close();
|
||||
}
|
||||
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue