Language updates. New upload form. new classes.

This commit is contained in:
Cody Cook 2025-02-22 00:20:39 -08:00
commit 8f3061ab99
62 changed files with 3107 additions and 1883 deletions

View file

@ -2,13 +2,19 @@
namespace DJMixHosting;
use DateTime;
use Exception;
use Random\RandomException;
use Aws\Ses\SesClient;
use Aws\Exception\AwsException;
Class User{
class User {
private $db;
private $id;
private $username;
private $firstName;
private $lastName;
private $email;
private $location;
private $bio;
@ -16,42 +22,83 @@ Class User{
private $updated;
private $verified;
private $role;
private $img;
private $img = "";
private $api_key;
public function __construct($db){
public function __construct($db, $id = null) {
$this->db = $db;
if ($id) {
$this->loadUserById($id);
}
}
/**
* @throws RandomException
* Load user data from the database by id.
*/
public function newUser($username, $password, $email){
if ($this->check_existing_user($username, $email)){
throw new RandomException("User already exists");
private function loadUserById($id) {
$stmt = $this->db->prepare("SELECT * FROM users WHERE id = ?");
$stmt->bind_param("i", $id);
$stmt->execute();
$user_data = $stmt->get_result()->fetch_assoc();
$stmt->close();
if ($user_data) {
$this->id = $user_data['id'];
$this->username = $user_data['username'];
$this->firstName = $user_data['firstName'];
$this->lastName = $user_data['lastName'];
$this->email = $user_data['email'];
$this->verified = $user_data['emailVerified'];
$this->img = $user_data['img'];
$this->api_key = $user_data['apiKey'];
$this->created = $user_data['created'];
$this->updated = $user_data['lastupdated'];
$this->role = $user_data['isAdmin'] ? 'admin' : 'user';
// These fields are not in your table; assign defaults or remove them.
$this->location = "";
$this->bio = "";
}
}
/**
* Register a new user.
*
* @throws RandomException if the user already exists.
*/
public function newUser(string $username, string $password, string $email, string $firstName, string $lastName): int {
if ($this->check_existing_user($username, $email)) {
throw new \Random\RandomException("User already exists");
}
$this->username = $username;
$this->email = $email;
$password2 = password_hash($password, PASSWORD_DEFAULT);
$this->password = $password2;
$this->firstName = $firstName;
$this->lastName = $lastName;
$password_hashed = password_hash($password, PASSWORD_DEFAULT);
$this->location = "";
$this->bio = "";
$this->created = date('Y-m-d H:i:s');
$this->updated = date('Y-m-d H:i:s');
$this->verified = 0;
$this->role = "user";
$this->img = "";
$this->api_key = bin2hex(random_bytes(32));
// Set default values for optional fields.
$this->location = "";
$this->bio = "";
$this->created = date('Y-m-d H:i:s');
$this->updated = date('Y-m-d H:i:s');
$this->verified = 0;
$this->role = "user";
$this->img = "";
$this->api_key = bin2hex(random_bytes(32));
$stmt = $this->db->prepare("INSERT INTO users (username, password, email, img) VALUES (?, ?, ?, ?)");
$stmt->bind_param("ssss", $this->username, $this->password, $this->email, $this->img);
$stmt = $this->db->prepare("INSERT INTO users (username, password, email, firstName, lastName, img, emailVerified) VALUES (?, ?, ?, ?, ?, '', 0)");
$stmt->bind_param("sssss", $this->username, $password_hashed, $this->email, $this->firstName, $this->lastName);
$stmt->execute();
$userId = $stmt->insert_id;
$stmt->close();
$this->id = $userId;
return $userId;
}
private function check_existing_user($username, $email){
private function check_existing_user($username, $email) {
$stmt = $this->db->prepare("SELECT * FROM users WHERE username = ? OR email = ?");
$stmt->bind_param("ss", $username, $email);
$stmt->execute();
@ -61,8 +108,13 @@ Class User{
return $user;
}
public function login($email, $password)
{
/**
* Login a user by email and password.
*
* Returns the user data array if successful. In case of failure,
* a string error message is returned.
*/
public function login($email, $password) {
// Retrieve user record by email
$stmt = $this->db->prepare("SELECT * FROM users WHERE email = ?");
$stmt->bind_param("s", $email);
@ -78,10 +130,10 @@ Class User{
$attempt_data = $stmt->get_result()->fetch_assoc();
$stmt->close();
$current_time = new \DateTime();
$current_time = new DateTime();
if ($attempt_data && !empty($attempt_data['lockout_until'])) {
$lockout_until = new \DateTime($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.";
}
@ -95,15 +147,10 @@ Class User{
// Verify the password using password_verify
if (password_verify($password, $user_data['password'])) {
// Successful login clear login attempts and set session variables
// Successful login clear login attempts.
$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;
// Return the user data for further session handling
return $user_data;
} else {
$attempts = $this->updateFailedAttempt($email);
return "Invalid email or password. Attempt $attempts of 3.";
@ -115,8 +162,7 @@ Class User{
* If attempts reach 3, set a lockout that doubles each time.
* Returns the current number of attempts.
*/
private function updateFailedAttempt($email)
{
private function updateFailedAttempt($email) {
// Check for an existing record
$stmt = $this->db->prepare("SELECT * FROM login_attempts WHERE email = ?");
$stmt->bind_param("s", $email);
@ -124,7 +170,7 @@ Class User{
$record = $stmt->get_result()->fetch_assoc();
$stmt->close();
$current_time = new \DateTime();
$current_time = new DateTime();
if ($record) {
$attempts = $record['attempts'] + 1;
@ -165,13 +211,186 @@ Class User{
/**
* Reset the login_attempts record for the given email.
*/
private function resetLoginAttempts($email)
{
private function resetLoginAttempts($email) {
$stmt = $this->db->prepare("DELETE FROM login_attempts WHERE email = ?");
$stmt->bind_param("s", $email);
$stmt->execute();
$stmt->close();
}
/**
* Update the user's email address.
*
* @param string $newEmail
* @param array $config Configuration array for AWS SES and app settings.
* @return string Success message.
* @throws \Exception on validation or email-sending failure.
*/
public function updateEmail(string $newEmail, array $config): string {
$newEmail = filter_var($newEmail, FILTER_VALIDATE_EMAIL);
if (!$newEmail) {
throw new \Exception("Invalid email format.");
}
}
// Update email and mark as unverified.
$stmt = $this->db->prepare("UPDATE users SET email = ?, emailVerified = 0 WHERE id = ?");
$stmt->bind_param("si", $newEmail, $this->id);
$stmt->execute();
$stmt->close();
// Generate verification code and expiry (15 minutes from now)
$verification_code = bin2hex(random_bytes(16));
$expires_at = date("Y-m-d H:i:s", strtotime("+15 minutes"));
// Store the verification record.
$stmt = $this->db->prepare("REPLACE INTO email_verifications (user_id, email, verification_code, expires_at) VALUES (?, ?, ?, ?)");
$stmt->bind_param("isss", $this->id, $newEmail, $verification_code, $expires_at);
$stmt->execute();
$stmt->close();
// Use the new Email class to send the verification email.
$emailObj = new Email($config);
$emailObj->sendVerificationEmail($newEmail, $verification_code);
// Update the class properties.
$this->email = $newEmail;
$this->verified = 0;
return "Email updated. A verification email has been sent to your new address.";
}
public function updateName($firstName, $lastName) {
// Update the user's name.
$stmt = $this->db->prepare("UPDATE users SET firstName = ?, lastName = ? WHERE id = ?");
$stmt->bind_param("ssi", $firstName, $lastName, $this->id);
if (!$stmt->execute()) {
$stmt->close();
throw new Exception("Failed to update name. Please try again.");
}
$stmt->close();
// Optionally update class properties.
$this->firstName = $firstName;
$this->lastName = $lastName;
return "Name updated successfully.";
}
public function updatePassword($currentPassword, $newPassword, $confirmPassword) {
// Retrieve the current password hash.
$stmt = $this->db->prepare("SELECT password FROM users WHERE id = ?");
$stmt->bind_param("i", $this->id);
$stmt->execute();
$userData = $stmt->get_result()->fetch_assoc();
$stmt->close();
if (!$userData || !password_verify($currentPassword, $userData['password'])) {
throw new Exception("Current password is incorrect.");
}
if ($newPassword !== $confirmPassword) {
throw new Exception("New password and confirmation do not match.");
}
// Validate the new password.
$pattern = '/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[\W_]).{8,32}$/';
if (!preg_match($pattern, $newPassword)) {
throw new Exception("New password must be 8-32 characters and include at least one uppercase letter, one lowercase letter, one number, and one symbol.");
}
$hashed_new_password = password_hash($newPassword, PASSWORD_DEFAULT);
$stmt = $this->db->prepare("UPDATE users SET password = ? WHERE id = ?");
$stmt->bind_param("si", $hashed_new_password, $this->id);
if (!$stmt->execute()) {
$stmt->close();
throw new Exception("Failed to update password. Please try again.");
}
$stmt->close();
return "Password updated successfully.";
}
public function updateUsername($newUsername) {
// Validate username format.
if (!preg_match('/^[a-zA-Z0-9_]{3,25}$/', $newUsername)) {
throw new Exception("Invalid username format.");
}
// Check if the new username already exists for another user.
$stmt = $this->db->prepare("SELECT id FROM users WHERE username = ? AND id != ?");
$stmt->bind_param("si", $newUsername, $this->id);
$stmt->execute();
$result = $stmt->get_result();
if ($result->num_rows > 0) {
$stmt->close();
throw new Exception("Username already taken.");
}
$stmt->close();
// Update the username.
$stmt = $this->db->prepare("UPDATE users SET username = ? WHERE id = ?");
$stmt->bind_param("si", $newUsername, $this->id);
$stmt->execute();
$stmt->close();
$this->username = $newUsername;
return "Username updated successfully.";
}
/**
* Verify the user's email using the provided verification code.
*
* @param string $verification_code The code submitted by the user.
* @return string Success message.
* @throws \Exception If the code is invalid or expired.
*/
public function verifyEmail(string $verification_code): string {
// Look up the verification record for this user and code
$stmt = $this->db->prepare("SELECT * FROM email_verifications WHERE user_id = ? AND verification_code = ?");
$stmt->bind_param("is", $this->id, $verification_code);
$stmt->execute();
$result = $stmt->get_result();
$record = $result->fetch_assoc();
$stmt->close();
if (!$record) {
throw new \Exception("Invalid verification code.");
}
// Check if the verification code has expired
$current_time = new \DateTime();
$expires_at = new \DateTime($record['expires_at']);
if ($current_time > $expires_at) {
throw new \Exception("Verification code has expired. Please request a new one.");
}
// Update the user's record to mark the email as verified
$stmt = $this->db->prepare("UPDATE users SET emailVerified = 1 WHERE id = ?");
$stmt->bind_param("i", $this->id);
$stmt->execute();
$stmt->close();
// Remove the verification record to clean up
$stmt = $this->db->prepare("DELETE FROM email_verifications WHERE user_id = ?");
$stmt->bind_param("i", $this->id);
$stmt->execute();
$stmt->close();
// Update the object property
$this->verified = 1;
return "Email verified successfully.";
}
// Getter methods
public function getId() { return $this->id; }
public function getUsername() { return $this->username; }
public function getFirstName() { return $this->firstName; }
public function getLastName() { return $this->lastName; }
public function getEmail() { return $this->email; }
public function getLocation() { return $this->location; }
public function getBio() { return $this->bio; }
public function getCreated() { return $this->created; }
public function getUpdated() { return $this->updated; }
public function getVerified() { return $this->verified; }
public function getRole() { return $this->role; }
public function getImg() { return $this->img; }
public function getApiKey() { return $this->api_key; }
}