widimulcing

AuthController.php

Jul 29th, 2025 (edited)
223
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
PHP 4.70 KB | None | 0 0
  1. <?php
  2. // 4o-mini-high
  3. /**
  4.  * @file        AuthController.php
  5.  * @description Handles HTTP requests for authentication. It processes user input,
  6.  * manages CSRF tokens, and interacts with the AuthService to perform
  7.  * the actual login logic. It is responsible for redirects and rendering views.
  8.  *
  9.  * @version     1.0.0
  10.  * @since       2025-07-25
  11.  * @author      Barrac0de
  12.  */
  13. declare(strict_types=1);
  14.  
  15. namespace App\Auth;
  16.  
  17. use PDO;
  18. use RuntimeException;
  19.  
  20. require_once __DIR__ . '/config.php';
  21. require_once __DIR__ . '/AuthService.php';
  22.  
  23. class AuthController
  24. {
  25.     private AuthService $authService;
  26.     private string $basePath;
  27.     private int $timeoutDuration = 3600; // session inactivity timeout
  28.  
  29.     public function __construct(PDO $pdo)
  30.     {
  31.         $this->authService = new AuthService($pdo);
  32.         // ensure BASE_PATH ends with a slash
  33.         $this->basePath = defined('BASE_PATH')
  34.             ? rtrim(BASE_PATH, '/') . '/'
  35.             : '/';
  36.     }
  37.  
  38.     public function run(): void
  39.     {
  40.         $this->startSession();
  41.         $this->sendSecurityHeaders();
  42.  
  43.         if ($_SERVER['REQUEST_METHOD'] === 'GET') {
  44.             $this->showLoginForm();
  45.         } elseif ($_SERVER['REQUEST_METHOD'] === 'POST') {
  46.             $this->processLogin();
  47.         } else {
  48.             http_response_code(405);
  49.             exit;
  50.         }
  51.     }
  52.  
  53.     private function startSession(): void
  54.     {
  55.         ini_set('session.use_strict_mode', '1');
  56.         if (session_status() === PHP_SESSION_NONE) {
  57.             ini_set('session.cookie_httponly', '1');
  58.             ini_set('session.cookie_secure',   '1');
  59.             ini_set('session.cookie_samesite', 'Lax');
  60.             session_start();
  61.         }
  62.     }
  63.  
  64.     private function sendSecurityHeaders(): void
  65.     {
  66.         header('Strict-Transport-Security: max-age=31536000; includeSubDomains; preload');
  67.         header('X-Frame-Options: DENY');
  68.         header('X-Content-Type-Options: nosniff');
  69.         header('Referrer-Policy: no-referrer');
  70.         header("Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self';");
  71.     }
  72.  
  73.     private function showLoginForm(): void
  74.     {
  75.         // if already logged in, go to dashboard
  76.         if (!empty($_SESSION['user_id'])) {
  77.             header('Location: ' . $this->basePath . 'index');
  78.             exit;
  79.         }
  80.  
  81.         // pull flash data
  82.         $error       = $_SESSION['login_error']  ?? '';
  83.         $oldUsername = $_SESSION['old_username'] ?? '';
  84.         unset($_SESSION['login_error'], $_SESSION['old_username']);
  85.  
  86.         // ensure CSRF token
  87.         if (empty($_SESSION['csrf_token'])) {
  88.             $_SESSION['csrf_token'] = bin2hex(random_bytes(32));
  89.         }
  90.  
  91.         require __DIR__ . '/../login.php';
  92.         exit;
  93.     }
  94.  
  95.     private function processLogin(): void
  96.     {
  97.         // session inactivity timeout
  98.         if (isset($_SESSION['last_activity'])
  99.             && (time() - $_SESSION['last_activity']) > $this->timeoutDuration
  100.         ) {
  101.             session_unset();
  102.             session_destroy();
  103.             header('Location: ' . $this->basePath . 'login?timeout=1');
  104.             exit;
  105.         }
  106.         $_SESSION['last_activity'] = time();
  107.  
  108.         // CSRF check
  109.         $csrfToken = filter_input(INPUT_POST, 'csrf_token', FILTER_SANITIZE_FULL_SPECIAL_CHARS) ?? '';
  110.         if (empty($_SESSION['csrf_token']) || !hash_equals($_SESSION['csrf_token'], $csrfToken)) {
  111.             $this->bounce('Invalid CSRF token.');
  112.         }
  113.  
  114.         // sanitize inputs
  115.         $username = trim((string)filter_input(INPUT_POST, 'username', FILTER_SANITIZE_FULL_SPECIAL_CHARS));
  116.         $password = (string)filter_input(INPUT_POST, 'password', FILTER_UNSAFE_RAW);
  117.  
  118.         if ($username === '' || $password === '') {
  119.             $this->bounce('Username and password are required.');
  120.         }
  121.  
  122.         // attempt authentication
  123.         try {
  124.             $userId = $this->authService->authenticate($username, $password);
  125.         } catch (RuntimeException $e) {
  126.             $this->bounce($e->getMessage());
  127.         }
  128.  
  129.         // on success
  130.         session_regenerate_id(true);
  131.         $_SESSION['user_id'] = $userId;
  132.         // rotate CSRF
  133.         $_SESSION['csrf_token'] = bin2hex(random_bytes(32));
  134.  
  135.         header('Location: ' . $this->basePath . 'index');
  136.         exit;
  137.     }
  138.  
  139.     private function bounce(string $message): void
  140.     {
  141.         // preserve username for UX
  142.         $old = trim((string)filter_input(INPUT_POST, 'username', FILTER_SANITIZE_FULL_SPECIAL_CHARS));
  143.         $_SESSION['old_username'] = $old;
  144.         $_SESSION['login_error']   = $message;
  145.         header('Location: ' . $this->basePath . 'login');
  146.         exit;
  147.     }
  148. }
  149.  
Advertisement
Add Comment
Please, Sign In to add comment