From bc54c8c31e7f50a7a365f9b4d22fe8c74a29f61a Mon Sep 17 00:00:00 2001 From: Thomas Vanbesien Date: Sat, 21 Mar 2026 21:35:51 +0100 Subject: Add user authentication with email verification and password reset Implements registration, login/logout, email verification via token, and password reset flow. Includes CSRF protection, flash messages, MailPit for dev email testing, and security docs in README. --- src/app/Models/User.php | 118 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 118 insertions(+) create mode 100644 src/app/Models/User.php (limited to 'src/app/Models') diff --git a/src/app/Models/User.php b/src/app/Models/User.php new file mode 100644 index 0000000..d4c5c88 --- /dev/null +++ b/src/app/Models/User.php @@ -0,0 +1,118 @@ +pdo = Database::getInstance()->getPdo(); + } + + public function create(string $username, string $email, string $password): int + { + $hash = password_hash($password, PASSWORD_DEFAULT); + $token = bin2hex(random_bytes(32)); + + $stmt = $this->pdo->prepare( + 'INSERT INTO users (username, email, password_hash, verification_token) + VALUES (:username, :email, :hash, :token)' + ); + $stmt->execute([ + 'username' => $username, + 'email' => $email, + 'hash' => $hash, + 'token' => $token, + ]); + + return (int) $this->pdo->lastInsertId(); + } + + public function findByUsername(string $username): ?array + { + $stmt = $this->pdo->prepare('SELECT * FROM users WHERE username = :username'); + $stmt->execute(['username' => $username]); + $row = $stmt->fetch(); + return $row ?: null; + } + + public function findByEmail(string $email): ?array + { + $stmt = $this->pdo->prepare('SELECT * FROM users WHERE email = :email'); + $stmt->execute(['email' => $email]); + $row = $stmt->fetch(); + return $row ?: null; + } + + public function findById(int $id): ?array + { + $stmt = $this->pdo->prepare('SELECT * FROM users WHERE id = :id'); + $stmt->execute(['id' => $id]); + $row = $stmt->fetch(); + return $row ?: null; + } + + public function findByVerificationToken(string $token): ?array + { + $stmt = $this->pdo->prepare('SELECT * FROM users WHERE verification_token = :token'); + $stmt->execute(['token' => $token]); + $row = $stmt->fetch(); + return $row ?: null; + } + + public function verify(int $id): void + { + $stmt = $this->pdo->prepare( + 'UPDATE users SET is_verified = TRUE, verification_token = NULL WHERE id = :id' + ); + $stmt->execute(['id' => $id]); + } + + public function setResetToken(int $id): string + { + $token = bin2hex(random_bytes(32)); + // Token expires in 1 hour + $expires = date('Y-m-d H:i:s', time() + 3600); + + $stmt = $this->pdo->prepare( + 'UPDATE users SET reset_token = :token, reset_token_expires = :expires WHERE id = :id' + ); + $stmt->execute(['token' => $token, 'expires' => $expires, 'id' => $id]); + + return $token; + } + + public function findByResetToken(string $token): ?array + { + $stmt = $this->pdo->prepare( + 'SELECT * FROM users WHERE reset_token = :token AND reset_token_expires > NOW()' + ); + $stmt->execute(['token' => $token]); + $row = $stmt->fetch(); + return $row ?: null; + } + + public function updatePassword(int $id, string $password): void + { + $hash = password_hash($password, PASSWORD_DEFAULT); + $stmt = $this->pdo->prepare( + 'UPDATE users SET password_hash = :hash, reset_token = NULL, reset_token_expires = NULL WHERE id = :id' + ); + $stmt->execute(['hash' => $hash, 'id' => $id]); + } + + public function getVerificationToken(int $id): ?string + { + $stmt = $this->pdo->prepare('SELECT verification_token FROM users WHERE id = :id'); + $stmt->execute(['id' => $id]); + $row = $stmt->fetch(); + return $row ? $row['verification_token'] : null; + } +} -- cgit v1.2.3