From 612a4bf790be57c272e261361c30bc919d20566f Mon Sep 17 00:00:00 2001 From: Hosteroid Date: Sun, 1 Feb 2026 12:30:16 +0200 Subject: [PATCH] Use POST for destructive actions & mobile UI tweaks Require POST and CSRF verification for destructive endpoints (profile delete, notification delete, clear-all) and update routes accordingly. Replace GET-based delete links with POST forms (including csrf_field()) and add hidden form submission for "clear all" and account deletion via JS. Add server-side request method checks and verifyCsrf() calls in NotificationController and ProfileController. Improve mobile UX: add sidebar overlay, open/close controls (including swipe-to-close), close button, prevent body scroll when sidebar open, responsive search placeholder and adjusted search/top-nav styling, and minor layout tweaks (truncate app name, adjust notification dropdown width). Also minor whitespace/formatting cleanups. --- app/Controllers/NotificationController.php | 18 ++++ app/Controllers/ProfileController.php | 17 +++- app/Views/layout/base.php | 96 +++++++++++++++++++++- app/Views/layout/sidebar.php | 12 ++- app/Views/layout/top-nav.php | 14 ++-- app/Views/notifications/index.php | 16 +++- app/Views/profile/index.php | 13 +-- routes/web.php | 6 +- 8 files changed, 163 insertions(+), 29 deletions(-) diff --git a/app/Controllers/NotificationController.php b/app/Controllers/NotificationController.php index 68df855..b7113ac 100644 --- a/app/Controllers/NotificationController.php +++ b/app/Controllers/NotificationController.php @@ -110,6 +110,15 @@ class NotificationController extends Controller */ public function delete($params = []) { + // Ensure POST method + if ($_SERVER['REQUEST_METHOD'] !== 'POST') { + $this->redirect('/notifications'); + return; + } + + // CSRF Protection + $this->verifyCsrf('/notifications'); + $userId = Auth::id(); $notificationId = (int)($params['id'] ?? 0); @@ -129,6 +138,15 @@ class NotificationController extends Controller */ public function clearAll() { + // Ensure POST method + if ($_SERVER['REQUEST_METHOD'] !== 'POST') { + $this->redirect('/notifications'); + return; + } + + // CSRF Protection + $this->verifyCsrf('/notifications'); + $userId = Auth::id(); $this->notificationModel->clearAll($userId); $_SESSION['success'] = 'All notifications cleared'; diff --git a/app/Controllers/ProfileController.php b/app/Controllers/ProfileController.php index bf379e8..e0750d1 100644 --- a/app/Controllers/ProfileController.php +++ b/app/Controllers/ProfileController.php @@ -232,6 +232,15 @@ class ProfileController extends Controller */ public function delete() { + // Ensure POST method + if ($_SERVER['REQUEST_METHOD'] !== 'POST') { + $this->redirect('/profile'); + return; + } + + // CSRF Protection + $this->verifyCsrf('/profile'); + $userId = Auth::id(); $user = $this->userModel->find($userId); @@ -243,14 +252,14 @@ class ProfileController extends Controller } // Delete user (cascade will handle related records) - $this->userModel->delete($userId); + $this->userModel->delete($userId); // Logout - session_destroy(); - session_start(); + session_destroy(); + session_start(); $_SESSION['success'] = 'Your account has been deleted'; - $this->redirect('/login'); + $this->redirect('/login'); } /** diff --git a/app/Views/layout/base.php b/app/Views/layout/base.php index 5fd240a..4dfed8a 100644 --- a/app/Views/layout/base.php +++ b/app/Views/layout/base.php @@ -93,7 +93,22 @@ if (!isset($appName)) { transition: transform 0.3s ease-in-out; } - @media (max-width: 768px) { + /* Mobile sidebar overlay */ + .sidebar-overlay { + position: fixed; + inset: 0; + background: rgba(0, 0, 0, 0.5); + z-index: 25; + opacity: 0; + visibility: hidden; + transition: opacity 0.3s ease-in-out, visibility 0.3s ease-in-out; + } + .sidebar-overlay.show { + opacity: 1; + visibility: visible; + } + + @media (max-width: 767px) { .sidebar { transform: translateX(-100%); } @@ -120,12 +135,27 @@ if (!isset($appName)) { background: #374151; border-left: 4px solid #4A90E2; } + + /* Mobile-friendly dropdown widths */ + @media (max-width: 480px) { + #notificationsDropdown { + width: calc(100vw - 2rem); + right: -0.5rem; + } + #userDropdown { + width: calc(100vw - 2rem); + right: -0.5rem; + } + } + + + @@ -145,8 +175,70 @@ if (!isset($appName)) { diff --git a/app/Views/profile/index.php b/app/Views/profile/index.php index 724f58e..c1f0f88 100644 --- a/app/Views/profile/index.php +++ b/app/Views/profile/index.php @@ -644,10 +644,13 @@ $avatar = \App\Helpers\AvatarHelper::getAvatar($user, 80); This action cannot be undone

- +
+ + +
@@ -734,7 +737,7 @@ document.addEventListener('DOMContentLoaded', function() { function confirmDelete() { if (confirm('Are you absolutely sure you want to delete your account?\n\nThis action is PERMANENT and cannot be undone!')) { if (confirm('FINAL WARNING: This will permanently delete all your data.\n\nClick OK to proceed.')) { - window.location.href = '/profile/delete'; + document.getElementById('deleteAccountForm').submit(); } } } diff --git a/routes/web.php b/routes/web.php index 4bb5cf0..43ea567 100644 --- a/routes/web.php +++ b/routes/web.php @@ -135,7 +135,7 @@ $router->post('/settings/toggle-isolation', [SettingsController::class, 'toggleI $router->get('/profile', [ProfileController::class, 'index']); $router->post('/profile/update', [ProfileController::class, 'update']); $router->post('/profile/change-password', [ProfileController::class, 'changePassword']); -$router->get('/profile/delete', [ProfileController::class, 'delete']); +$router->post('/profile/delete', [ProfileController::class, 'delete']); $router->get('/profile/resend-verification', [ProfileController::class, 'resendVerification']); $router->post('/profile/logout-other-sessions', [ProfileController::class, 'logoutOtherSessions']); $router->post('/profile/logout-session/{sessionId}', [ProfileController::class, 'logoutSession']); @@ -154,8 +154,8 @@ $router->post('/2fa/regenerate-backup-codes', [TwoFactorController::class, 'rege $router->get('/notifications', [NotificationController::class, 'index']); $router->get('/notifications/{id}/mark-read', [NotificationController::class, 'markAsRead']); $router->get('/notifications/mark-all-read', [NotificationController::class, 'markAllAsRead']); -$router->get('/notifications/{id}/delete', [NotificationController::class, 'delete']); -$router->get('/notifications/clear-all', [NotificationController::class, 'clearAll']); +$router->post('/notifications/{id}/delete', [NotificationController::class, 'delete']); +$router->post('/notifications/clear-all', [NotificationController::class, 'clearAll']); $router->get('/api/notifications/unread-count', [NotificationController::class, 'getUnreadCount']); $router->get('/api/notifications/recent', [NotificationController::class, 'getRecent']);