547 lines
18 KiB
PHP
547 lines
18 KiB
PHP
<?php
|
|
/*******************************************************************************************************************
|
|
| Software Name : EasyStream
|
|
| Software Description : High End YouTube Clone Script with Videos, Shorts, Streams, Images, Audio, Documents, Blogs
|
|
| Software Author : (c) Sami Ahmed
|
|
|*******************************************************************************************************************
|
|
|
|
|
|*******************************************************************************************************************
|
|
| This source file is subject to the EasyStream Proprietary License Agreement.
|
|
|
|
|
| By using this software, you acknowledge having read this Agreement and agree to be bound thereby.
|
|
|*******************************************************************************************************************
|
|
| Copyright (c) 2025 Sami Ahmed. All rights reserved.
|
|
|*******************************************************************************************************************/
|
|
|
|
defined('_ISVALID') or header('Location: /error');
|
|
|
|
/**
|
|
* Middleware System for Route Protection and Request Processing
|
|
*/
|
|
class VMiddleware
|
|
{
|
|
private static $instance = null;
|
|
private $auth;
|
|
private $rbac;
|
|
private $security;
|
|
private $logger;
|
|
|
|
public static function getInstance()
|
|
{
|
|
if (self::$instance === null) {
|
|
self::$instance = new self();
|
|
}
|
|
return self::$instance;
|
|
}
|
|
|
|
private function __construct()
|
|
{
|
|
$this->auth = VAuth::getInstance();
|
|
$this->rbac = VRBAC::getInstance();
|
|
$this->security = VSecurity::getInstance();
|
|
$this->logger = VLogger::getInstance();
|
|
}
|
|
|
|
/**
|
|
* Authentication middleware - requires user to be logged in
|
|
* @param callable|null $callback Optional callback to execute if authenticated
|
|
* @return bool True if authenticated
|
|
*/
|
|
public function requireAuth($callback = null)
|
|
{
|
|
if (!$this->auth->isAuthenticated()) {
|
|
$this->handleUnauthenticated();
|
|
return false;
|
|
}
|
|
|
|
if ($callback && is_callable($callback)) {
|
|
return $callback();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Guest middleware - requires user to NOT be logged in
|
|
* @param callable|null $callback Optional callback to execute if guest
|
|
* @return bool True if guest
|
|
*/
|
|
public function requireGuest($callback = null)
|
|
{
|
|
if ($this->auth->isAuthenticated()) {
|
|
$this->handleAlreadyAuthenticated();
|
|
return false;
|
|
}
|
|
|
|
if ($callback && is_callable($callback)) {
|
|
return $callback();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Role middleware - requires specific role or higher
|
|
* @param string $requiredRole Required role
|
|
* @param callable|null $callback Optional callback to execute if authorized
|
|
* @return bool True if authorized
|
|
*/
|
|
public function requireRole($requiredRole, $callback = null)
|
|
{
|
|
if (!$this->rbac->requireRole($requiredRole)) {
|
|
return false;
|
|
}
|
|
|
|
if ($callback && is_callable($callback)) {
|
|
return $callback();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Permission middleware - requires specific permission
|
|
* @param string|array $requiredPermissions Required permission(s)
|
|
* @param array $context Additional context for permission check
|
|
* @param callable|null $callback Optional callback to execute if authorized
|
|
* @return bool True if authorized
|
|
*/
|
|
public function requirePermission($requiredPermissions, $context = [], $callback = null)
|
|
{
|
|
if (!$this->rbac->requirePermission($requiredPermissions, $context)) {
|
|
return false;
|
|
}
|
|
|
|
if ($callback && is_callable($callback)) {
|
|
return $callback();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* CSRF middleware - validates CSRF token
|
|
* @param string $action CSRF action name
|
|
* @param callable|null $callback Optional callback to execute if valid
|
|
* @return bool True if valid
|
|
*/
|
|
public function requireCSRF($action = 'default', $callback = null)
|
|
{
|
|
if (!VSecurity::validateCSRFFromPost($action)) {
|
|
$this->handleCSRFFailure();
|
|
return false;
|
|
}
|
|
|
|
if ($callback && is_callable($callback)) {
|
|
return $callback();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Rate limiting middleware
|
|
* @param string $key Rate limit key
|
|
* @param int $maxAttempts Maximum attempts
|
|
* @param int $timeWindow Time window in seconds
|
|
* @param string $action Action being rate limited
|
|
* @param callable|null $callback Optional callback to execute if within limits
|
|
* @return bool True if within limits
|
|
*/
|
|
public function requireRateLimit($key, $maxAttempts = 10, $timeWindow = 300, $action = 'request', $callback = null)
|
|
{
|
|
if (!VSecurity::checkRateLimit($key, $maxAttempts, $timeWindow, $action)) {
|
|
$this->handleRateLimitExceeded($action);
|
|
return false;
|
|
}
|
|
|
|
if ($callback && is_callable($callback)) {
|
|
return $callback();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Content ownership middleware - checks if user owns the content
|
|
* @param int $contentId Content ID
|
|
* @param string $contentType Content type (video, stream, etc.)
|
|
* @param callable|null $callback Optional callback to execute if owner
|
|
* @return bool True if owner or has permission
|
|
*/
|
|
public function requireContentOwnership($contentId, $contentType = 'content', $callback = null)
|
|
{
|
|
$user = $this->auth->getCurrentUser();
|
|
if (!$user) {
|
|
$this->handleUnauthenticated();
|
|
return false;
|
|
}
|
|
|
|
// Check if user owns the content
|
|
$ownerId = $this->getContentOwner($contentId, $contentType);
|
|
if ($ownerId === $user['user_id']) {
|
|
if ($callback && is_callable($callback)) {
|
|
return $callback();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// Check if user has moderation permissions
|
|
$moderationPermission = $contentType . '.moderate';
|
|
if ($this->rbac->hasPermission($moderationPermission)) {
|
|
if ($callback && is_callable($callback)) {
|
|
return $callback();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
$this->handleAccessDenied("Content ownership or {$moderationPermission} permission required");
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Email verification middleware
|
|
* @param callable|null $callback Optional callback to execute if verified
|
|
* @return bool True if email is verified
|
|
*/
|
|
public function requireEmailVerification($callback = null)
|
|
{
|
|
$user = $this->auth->getCurrentUser();
|
|
if (!$user) {
|
|
$this->handleUnauthenticated();
|
|
return false;
|
|
}
|
|
|
|
// Check if email verification is required
|
|
global $cfg;
|
|
$requireVerification = $cfg['require_email_verification'] ?? true;
|
|
|
|
if (!$requireVerification) {
|
|
if ($callback && is_callable($callback)) {
|
|
return $callback();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// Get user data to check verification status
|
|
global $class_database;
|
|
$db = $class_database->dbConnection();
|
|
$sql = "SELECT email_verified FROM db_users WHERE user_id = ?";
|
|
$result = $db->Execute($sql, [$user['user_id']]);
|
|
|
|
if (!$result || $result->EOF || !$result->fields['email_verified']) {
|
|
$this->handleEmailNotVerified();
|
|
return false;
|
|
}
|
|
|
|
if ($callback && is_callable($callback)) {
|
|
return $callback();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Account status middleware - checks if account is active
|
|
* @param callable|null $callback Optional callback to execute if active
|
|
* @return bool True if account is active
|
|
*/
|
|
public function requireActiveAccount($callback = null)
|
|
{
|
|
$user = $this->auth->getCurrentUser();
|
|
if (!$user) {
|
|
$this->handleUnauthenticated();
|
|
return false;
|
|
}
|
|
|
|
// Get current user status
|
|
global $class_database;
|
|
$db = $class_database->dbConnection();
|
|
$sql = "SELECT status FROM db_users WHERE user_id = ?";
|
|
$result = $db->Execute($sql, [$user['user_id']]);
|
|
|
|
if (!$result || $result->EOF) {
|
|
$this->handleUnauthenticated();
|
|
return false;
|
|
}
|
|
|
|
$status = $result->fields['status'];
|
|
|
|
if ($status !== 'active') {
|
|
$this->handleInactiveAccount($status);
|
|
return false;
|
|
}
|
|
|
|
if ($callback && is_callable($callback)) {
|
|
return $callback();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* API middleware - validates API access
|
|
* @param bool $requireAuth Whether authentication is required
|
|
* @param callable|null $callback Optional callback to execute if valid
|
|
* @return bool True if valid API request
|
|
*/
|
|
public function requireAPI($requireAuth = true, $callback = null)
|
|
{
|
|
// Check if API access is enabled
|
|
global $cfg;
|
|
$apiEnabled = $cfg['api_enabled'] ?? true;
|
|
|
|
if (!$apiEnabled) {
|
|
$this->sendAPIResponse(['success' => false, 'message' => 'API access is disabled'], 503);
|
|
return false;
|
|
}
|
|
|
|
// Check API rate limiting
|
|
$clientIP = $this->getClientIP();
|
|
$rateLimitKey = "api_requests_{$clientIP}";
|
|
$maxRequests = $cfg['api_rate_limit'] ?? 1000;
|
|
$timeWindow = $cfg['api_rate_window'] ?? 3600;
|
|
|
|
if (!VSecurity::checkRateLimit($rateLimitKey, $maxRequests, $timeWindow, 'api_request')) {
|
|
$this->sendAPIResponse(['success' => false, 'message' => 'API rate limit exceeded'], 429);
|
|
return false;
|
|
}
|
|
|
|
// Check authentication if required
|
|
if ($requireAuth) {
|
|
if (!$this->auth->isAuthenticated()) {
|
|
$this->sendAPIResponse(['success' => false, 'message' => 'Authentication required'], 401);
|
|
return false;
|
|
}
|
|
|
|
// Check API permission
|
|
if (!$this->rbac->hasPermission('api.access')) {
|
|
$this->sendAPIResponse(['success' => false, 'message' => 'API access permission required'], 403);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if ($callback && is_callable($callback)) {
|
|
return $callback();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Combine multiple middleware checks
|
|
* @param array $middlewares Array of middleware configurations
|
|
* @param callable|null $callback Optional callback to execute if all pass
|
|
* @return bool True if all middleware checks pass
|
|
*/
|
|
public function requireAll($middlewares, $callback = null)
|
|
{
|
|
foreach ($middlewares as $middleware) {
|
|
$method = $middleware['method'];
|
|
$params = $middleware['params'] ?? [];
|
|
|
|
if (!call_user_func_array([$this, $method], $params)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if ($callback && is_callable($callback)) {
|
|
return $callback();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Helper method to create middleware chain
|
|
* @param array $middlewares Array of middleware configurations
|
|
* @return callable Middleware chain function
|
|
*/
|
|
public static function chain($middlewares)
|
|
{
|
|
return function($callback = null) use ($middlewares) {
|
|
$middleware = VMiddleware::getInstance();
|
|
return $middleware->requireAll($middlewares, $callback);
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Private helper methods
|
|
*/
|
|
|
|
private function handleUnauthenticated()
|
|
{
|
|
$this->logger->info('Unauthenticated access attempt', [
|
|
'url' => $_SERVER['REQUEST_URI'] ?? '',
|
|
'ip' => $this->getClientIP(),
|
|
'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? ''
|
|
]);
|
|
|
|
if ($this->isAPIRequest()) {
|
|
$this->sendAPIResponse(['success' => false, 'message' => 'Authentication required'], 401);
|
|
} else {
|
|
$redirectUrl = urlencode($_SERVER['REQUEST_URI'] ?? '/');
|
|
if (strpos($_SERVER["REQUEST_URI"] ?? "", "/signin") === 0) { http_response_code(401); return; }
|
|
header("Location: /signin?redirect={$redirectUrl}");
|
|
exit;
|
|
}
|
|
}
|
|
|
|
private function handleAlreadyAuthenticated()
|
|
{
|
|
if ($this->isAPIRequest()) {
|
|
$this->sendAPIResponse(['success' => false, 'message' => 'Already authenticated'], 400);
|
|
} else {
|
|
header('Location: /dashboard');
|
|
exit;
|
|
}
|
|
}
|
|
|
|
private function handleCSRFFailure()
|
|
{
|
|
$this->logger->logSecurityEvent('CSRF token validation failed', [
|
|
'url' => $_SERVER['REQUEST_URI'] ?? '',
|
|
'ip' => $this->getClientIP(),
|
|
'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? ''
|
|
]);
|
|
|
|
if ($this->isAPIRequest()) {
|
|
$this->sendAPIResponse(['success' => false, 'message' => 'Invalid CSRF token'], 403);
|
|
} else {
|
|
header('Location: /error?type=csrf');
|
|
exit;
|
|
}
|
|
}
|
|
|
|
private function handleRateLimitExceeded($action)
|
|
{
|
|
$this->logger->logSecurityEvent('Rate limit exceeded', [
|
|
'action' => $action,
|
|
'ip' => $this->getClientIP(),
|
|
'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? ''
|
|
]);
|
|
|
|
if ($this->isAPIRequest()) {
|
|
$this->sendAPIResponse(['success' => false, 'message' => 'Rate limit exceeded'], 429);
|
|
} else {
|
|
header('Location: /error?type=rate_limit');
|
|
exit;
|
|
}
|
|
}
|
|
|
|
private function handleAccessDenied($message = 'Access denied')
|
|
{
|
|
if ($this->isAPIRequest()) {
|
|
$this->sendAPIResponse(['success' => false, 'message' => $message], 403);
|
|
} else {
|
|
header('Location: /access-denied');
|
|
exit;
|
|
}
|
|
}
|
|
|
|
private function handleEmailNotVerified()
|
|
{
|
|
if ($this->isAPIRequest()) {
|
|
$this->sendAPIResponse(['success' => false, 'message' => 'Email verification required'], 403);
|
|
} else {
|
|
header('Location: /verify-email');
|
|
exit;
|
|
}
|
|
}
|
|
|
|
private function handleInactiveAccount($status)
|
|
{
|
|
$message = "Account is {$status}";
|
|
|
|
if ($this->isAPIRequest()) {
|
|
$this->sendAPIResponse(['success' => false, 'message' => $message], 403);
|
|
} else {
|
|
header("Location: /account-status?status={$status}");
|
|
exit;
|
|
}
|
|
}
|
|
|
|
private function getContentOwner($contentId, $contentType)
|
|
{
|
|
global $class_database;
|
|
$db = $class_database->dbConnection();
|
|
|
|
$table = $this->getContentTable($contentType);
|
|
$idField = $this->getContentIdField($contentType);
|
|
|
|
$sql = "SELECT user_id FROM {$table} WHERE {$idField} = ?";
|
|
$result = $db->Execute($sql, [$contentId]);
|
|
|
|
return ($result && !$result->EOF) ? $result->fields['user_id'] : null;
|
|
}
|
|
|
|
private function getContentTable($contentType)
|
|
{
|
|
$tables = [
|
|
'video' => 'db_videofiles',
|
|
'stream' => 'db_livefiles',
|
|
'image' => 'db_imagefiles',
|
|
'audio' => 'db_audiofiles',
|
|
'document' => 'db_documentfiles',
|
|
'blog' => 'db_blogfiles'
|
|
];
|
|
|
|
return $tables[$contentType] ?? 'db_videofiles';
|
|
}
|
|
|
|
private function getContentIdField($contentType)
|
|
{
|
|
$fields = [
|
|
'video' => 'video_id',
|
|
'stream' => 'stream_id',
|
|
'image' => 'image_id',
|
|
'audio' => 'audio_id',
|
|
'document' => 'document_id',
|
|
'blog' => 'blog_id'
|
|
];
|
|
|
|
return $fields[$contentType] ?? 'video_id';
|
|
}
|
|
|
|
private function isAPIRequest()
|
|
{
|
|
return strpos($_SERVER['REQUEST_URI'] ?? '', '/api/') === 0 ||
|
|
strpos($_SERVER['HTTP_ACCEPT'] ?? '', 'application/json') !== false;
|
|
}
|
|
|
|
private function sendAPIResponse($data, $statusCode = 200)
|
|
{
|
|
header('Content-Type: application/json');
|
|
http_response_code($statusCode);
|
|
echo json_encode($data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
|
|
exit;
|
|
}
|
|
|
|
private function getClientIP()
|
|
{
|
|
$ipKeys = [
|
|
'HTTP_CF_CONNECTING_IP',
|
|
'HTTP_X_FORWARDED_FOR',
|
|
'HTTP_X_FORWARDED',
|
|
'HTTP_X_CLUSTER_CLIENT_IP',
|
|
'HTTP_FORWARDED_FOR',
|
|
'HTTP_FORWARDED',
|
|
'REMOTE_ADDR'
|
|
];
|
|
|
|
foreach ($ipKeys as $key) {
|
|
if (array_key_exists($key, $_SERVER) === true) {
|
|
$ip = $_SERVER[$key];
|
|
if (strpos($ip, ',') !== false) {
|
|
$ip = explode(',', $ip)[0];
|
|
}
|
|
$ip = trim($ip);
|
|
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
|
|
return $ip;
|
|
}
|
|
}
|
|
}
|
|
|
|
return $_SERVER['REMOTE_ADDR'] ?? 'unknown';
|
|
}
|
|
} |