aboutsummaryrefslogtreecommitdiffstats
path: root/src/app/Controllers
diff options
context:
space:
mode:
authorThomas Vanbesien <tvanbesi@proton.me>2026-03-21 21:49:57 +0100
committerThomas Vanbesien <tvanbesi@proton.me>2026-03-21 21:49:57 +0100
commitf60a390f5c51039fd1efc1df9a6a7f3864ce0062 (patch)
tree9ec829dd92a93a79a2047494d07c95b7c0197389 /src/app/Controllers
parentbc54c8c31e7f50a7a365f9b4d22fe8c74a29f61a (diff)
downloadcamagru-f60a390f5c51039fd1efc1df9a6a7f3864ce0062.tar.gz
camagru-f60a390f5c51039fd1efc1df9a6a7f3864ce0062.zip
Add profile page for editing username, email, password, and notifications
Diffstat (limited to 'src/app/Controllers')
-rw-r--r--src/app/Controllers/AuthController.php8
-rw-r--r--src/app/Controllers/ProfileController.php176
2 files changed, 176 insertions, 8 deletions
diff --git a/src/app/Controllers/AuthController.php b/src/app/Controllers/AuthController.php
index ae94295..aad40be 100644
--- a/src/app/Controllers/AuthController.php
+++ b/src/app/Controllers/AuthController.php
@@ -19,8 +19,6 @@ class AuthController
$this->user = new User();
}
- // ── Registration ──
-
public function registerForm(): void
{
$content = __DIR__ . '/../Views/auth/register.php';
@@ -79,8 +77,6 @@ class AuthController
header('Location: /login');
}
- // ── Email verification ──
-
public function verify(): void
{
$token = $_GET['token'] ?? '';
@@ -97,8 +93,6 @@ class AuthController
header('Location: /login');
}
- // ── Login / Logout ──
-
public function loginForm(): void
{
$content = __DIR__ . '/../Views/auth/login.php';
@@ -146,8 +140,6 @@ class AuthController
header('Location: /');
}
- // ── Password reset ──
-
public function forgotPasswordForm(): void
{
$content = __DIR__ . '/../Views/auth/forgot-password.php';
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');
+ }
+}