diff options
| author | Thomas Vanbesien <tvanbesi@proton.me> | 2026-03-21 21:49:57 +0100 |
|---|---|---|
| committer | Thomas Vanbesien <tvanbesi@proton.me> | 2026-03-21 21:49:57 +0100 |
| commit | f60a390f5c51039fd1efc1df9a6a7f3864ce0062 (patch) | |
| tree | 9ec829dd92a93a79a2047494d07c95b7c0197389 /src/app/Controllers/ProfileController.php | |
| parent | bc54c8c31e7f50a7a365f9b4d22fe8c74a29f61a (diff) | |
| download | camagru-f60a390f5c51039fd1efc1df9a6a7f3864ce0062.tar.gz camagru-f60a390f5c51039fd1efc1df9a6a7f3864ce0062.zip | |
Add profile page for editing username, email, password, and notifications
Diffstat (limited to 'src/app/Controllers/ProfileController.php')
| -rw-r--r-- | src/app/Controllers/ProfileController.php | 176 |
1 files changed, 176 insertions, 0 deletions
diff --git a/src/app/Controllers/ProfileController.php b/src/app/Controllers/ProfileController.php new file mode 100644 index 0000000..45a9d28 --- /dev/null +++ b/src/app/Controllers/ProfileController.php @@ -0,0 +1,176 @@ +<?php + +declare(strict_types=1); +// Profile management: lets the logged-in user change username, email, password, +// and notification preferences. + +namespace App\Controllers; + +use App\Csrf; +use App\Flash; +use App\Mail; +use App\Models\User; + +class ProfileController +{ + private User $user; + + public function __construct() + { + $this->user = new User(); + } + + public function show(): void + { + if (!isset($_SESSION['user_id'])) { + header('Location: /login'); + return; + } + + $user = $this->user->findById($_SESSION['user_id']); + $content = __DIR__ . '/../Views/profile/edit.php'; + include __DIR__ . '/../Views/layouts/main.php'; + } + + public function updateUsername(): void + { + if (!isset($_SESSION['user_id'])) { + header('Location: /login'); + return; + } + + if (!Csrf::validate($_POST['csrf_token'] ?? '')) { + Flash::set('error', 'Invalid CSRF token.'); + header('Location: /profile'); + return; + } + + $username = trim($_POST['username'] ?? ''); + + if (!preg_match('/^[a-zA-Z0-9_]{3,20}$/', $username)) { + Flash::set('error', 'Username must be 3-20 characters (letters, numbers, underscores).'); + header('Location: /profile'); + return; + } + + $existing = $this->user->findByUsername($username); + if ($existing && $existing['id'] !== $_SESSION['user_id']) { + Flash::set('error', 'Username is already taken.'); + header('Location: /profile'); + return; + } + + $this->user->updateUsername($_SESSION['user_id'], $username); + // Keep the session in sync so the nav bar shows the new name + $_SESSION['username'] = $username; + + Flash::set('success', 'Username updated.'); + header('Location: /profile'); + } + + public function updateEmail(): void + { + if (!isset($_SESSION['user_id'])) { + header('Location: /login'); + return; + } + + if (!Csrf::validate($_POST['csrf_token'] ?? '')) { + Flash::set('error', 'Invalid CSRF token.'); + header('Location: /profile'); + return; + } + + $email = trim($_POST['email'] ?? ''); + + if (!filter_var($email, FILTER_VALIDATE_EMAIL)) { + Flash::set('error', 'Invalid email address.'); + header('Location: /profile'); + return; + } + + $existing = $this->user->findByEmail($email); + if ($existing && $existing['id'] !== $_SESSION['user_id']) { + Flash::set('error', 'Email is already registered.'); + header('Location: /profile'); + return; + } + + // Changing email requires re-verification: the user must confirm + // they own the new address before they can log in again + $token = $this->user->updateEmail($_SESSION['user_id'], $email); + Mail::sendVerification($email, $token); + + // Log the user out so they must re-verify + session_destroy(); + // Start a fresh session for the flash message + session_start(); + Flash::set('success', 'Email updated. Check your new email to re-verify your account.'); + header('Location: /login'); + } + + public function updatePassword(): void + { + if (!isset($_SESSION['user_id'])) { + header('Location: /login'); + return; + } + + if (!Csrf::validate($_POST['csrf_token'] ?? '')) { + Flash::set('error', 'Invalid CSRF token.'); + header('Location: /profile'); + return; + } + + $currentPassword = $_POST['current_password'] ?? ''; + $newPassword = $_POST['new_password'] ?? ''; + $confirmPassword = $_POST['new_password_confirm'] ?? ''; + + $user = $this->user->findById($_SESSION['user_id']); + + // Verify the current password so an attacker who steals a session + // can't silently change the password + if (!password_verify($currentPassword, $user['password_hash'])) { + Flash::set('error', 'Current password is incorrect.'); + header('Location: /profile'); + return; + } + + if (\strlen($newPassword) < 8) { + Flash::set('error', 'New password must be at least 8 characters.'); + header('Location: /profile'); + return; + } + + if ($newPassword !== $confirmPassword) { + Flash::set('error', 'New passwords do not match.'); + header('Location: /profile'); + return; + } + + $this->user->updatePassword($user['id'], $newPassword); + Flash::set('success', 'Password updated.'); + header('Location: /profile'); + } + + public function updateNotifications(): void + { + if (!isset($_SESSION['user_id'])) { + header('Location: /login'); + return; + } + + if (!Csrf::validate($_POST['csrf_token'] ?? '')) { + Flash::set('error', 'Invalid CSRF token.'); + header('Location: /profile'); + return; + } + + // Checkbox value: present in POST when checked, absent when unchecked + $notify = isset($_POST['notify_comments']); + $this->user->updateNotifyComments($_SESSION['user_id'], $notify); + + Flash::set('success', 'Notification preferences updated.'); + header('Location: /profile'); + } +} |
