Initial commit

This commit is contained in:
Porawit Dongwang
2026-09-16 23:20:08 +07:00
commit 0041668dbb
32577 changed files with 3687927 additions and 0 deletions
@@ -0,0 +1,31 @@
<?php
namespace App\Middleware;
use App\Helpers\Response;
use App\Helpers\Security;
/**
* Class CsrfMiddleware
* Cross-Site Request Forgery (CSRF) Protection Middleware
*
* @package App\Middleware
*/
class CsrfMiddleware
{
/**
* Verify CSRF token for state-changing requests (POST, PUT, DELETE)
*/
public static function handle(): void
{
$method = $_SERVER['REQUEST_METHOD'] ?? 'GET';
if (in_array($method, ['POST', 'PUT', 'DELETE', 'PATCH'], true)) {
$headers = getallheaders();
$token = $headers['X-CSRF-Token'] ?? $headers['x-csrf-token'] ?? $_POST['_csrf_token'] ?? null;
if (!Security::verifyCsrfToken($token)) {
Response::error("CSRF Token Validation Failed: Request is forged or session expired (กรุณารีเฟรชหน้าเว็บ)", 403);
}
}
}
}
@@ -0,0 +1,64 @@
<?php
namespace App\Middleware;
use App\Helpers\Response;
use App\Helpers\Security;
use App\Models\User;
/**
* Class JwtAuthMiddleware
* Enterprise JWT Authentication & Session Validation Middleware
*
* @package App\Middleware
*/
class JwtAuthMiddleware
{
/**
* Handle incoming request validation
*
* @return array Authenticated User Data
*/
public static function handle(): array
{
$headers = getallheaders();
$authHeader = $headers['Authorization'] ?? $headers['authorization'] ?? $_SERVER['HTTP_AUTHORIZATION'] ?? '';
$token = null;
if (preg_match('/^Bearer\s+(.*?)$/i', $authHeader, $matches)) {
$token = trim($matches[1]);
} elseif (!empty($_SESSION['access_token'])) {
$token = $_SESSION['access_token'];
}
if (empty($token)) {
Response::error('Unauthorized: No authentication token provided (กรุณาเข้าสู่ระบบ)', 401);
}
$config = require __DIR__ . '/../../config/security.php';
$secret = $config['jwt']['secret'];
$payload = Security::decodeJwt($token, $secret);
if (!$payload || !isset($payload['sub'])) {
Response::error('Unauthorized: Invalid or expired token (Token หมดอายุหรือไม่ถูกต้อง)', 401);
}
// Validate user existence and status in DB
$userModel = new User();
$user = $userModel->find((int)$payload['sub']);
if (!$user || (int)$user['is_active'] !== 1) {
Response::error('Unauthorized: User account is inactive or deleted', 401);
}
// Set global session context
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
$_SESSION['user_id'] = (int)$user['id'];
$_SESSION['user_role'] = $user['role'];
$_SESSION['branch_id'] = (int)($user['branch_id'] ?? 1);
unset($user['password_hash'], $user['two_factor_secret'], $user['two_factor_recovery_codes']);
return $user;
}
}
@@ -0,0 +1,49 @@
<?php
namespace App\Middleware;
use App\Helpers\Response;
use App\Helpers\Security;
use App\Models\Model;
/**
* Class RateLimitMiddleware
* Enterprise API Rate Limiter & DDOS Mitigation Guard
*
* @package App\Middleware
*/
class RateLimitMiddleware
{
/**
* Handle rate limiting check (Max 60 requests per minute per IP)
*/
public static function handle(int $maxRequests = 60, int $windowSeconds = 60): void
{
$ip = Security::getClientIp();
$key = "rate_limit:ip:" . md5($ip);
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
// Use Session / APCu / Redis / File-based tracking for clean fallback without crashing if Redis is offline
$now = time();
$record = $_SESSION[$key] ?? ['count' => 0, 'start' => $now];
if (($now - $record['start']) >= $windowSeconds) {
$record = ['count' => 1, 'start' => $now];
} else {
$record['count']++;
}
$_SESSION[$key] = $record;
// Set Rate Limit Headers
header("X-RateLimit-Limit: {$maxRequests}");
header("X-RateLimit-Remaining: " . max(0, $maxRequests - $record['count']));
header("X-RateLimit-Reset: " . ($record['start'] + $windowSeconds));
if ($record['count'] > $maxRequests) {
Response::error("Too Many Requests: ท่านส่งคำขอเข้ามาถี่เกินไป กรุณารอ 1 นาทีก่อนลองใหม่อีกครั้ง (DDOS Protection)", 429);
}
}
}
@@ -0,0 +1,31 @@
<?php
namespace App\Middleware;
use App\Helpers\Response;
/**
* Class RoleGuard
* Role-Based Access Control (RBAC) Guard Middleware
*
* @package App\Middleware
*/
class RoleGuard
{
/**
* Verify if current authenticated user has one of the required roles
*
* @param array $allowedRoles e.g. ['Admin', 'Manager', 'Doctor']
*/
public static function check(array $allowedRoles): void
{
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
$userRole = $_SESSION['user_role'] ?? null;
if (!$userRole || !in_array($userRole, $allowedRoles, true)) {
Response::error("Forbidden: Your role ({$userRole}) does not have permission to access this resource (คุณไม่มีสิทธิ์เข้าถึงฟังก์ชันนี้)", 403);
}
}
}
@@ -0,0 +1,42 @@
<?php
namespace App\Middleware;
use App\Helpers\Response;
use App\Models\User;
/**
* Class TwoFactorGuard
* 2FA (Google Authenticator TOTP) Verification Guard Middleware
*
* @package App\Middleware
*/
class TwoFactorGuard
{
/**
* Ensure user has verified 2FA if enabled on their account
*/
public static function check(): void
{
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
$userId = $_SESSION['user_id'] ?? null;
if (!$userId) {
Response::error("Unauthorized: Please login first", 401);
}
$userModel = new User();
$user = $userModel->find((int)$userId);
if ($user && (int)$user['two_factor_enabled'] === 1) {
$isVerified = $_SESSION['2fa_verified'] ?? false;
if (!$isVerified) {
Response::error("2FA Required: บัญชีของท่านเปิดใช้งาน 2-Factor Authentication กรุณายืนยันรหัส TOTP 6 หลักจาก Google Authenticator ก่อนใช้งาน", 403, [
'require_2fa' => true,
'redirect' => '/verify-2fa',
]);
}
}
}
}