Skip to content

Commit

Permalink
update files
Browse files Browse the repository at this point in the history
  • Loading branch information
juancristobalgd1 committed Feb 29, 2024
1 parent b99e698 commit 6e81957
Show file tree
Hide file tree
Showing 9 changed files with 397 additions and 10 deletions.
6 changes: 3 additions & 3 deletions app/middlewares/BaseMiddleware.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ abstract class BaseMiddleware
* @var array
*/
public static $httpMiddlewares = [
\Axm\Middlewares\MaintenanceMiddleware::class,
\Axm\Middlewares\VerifyCsrfTokenMiddleware::class,
\Axm\Middlewares\AuthMiddleware::class,
\Middlewares\MaintenanceMiddleware::class,
\Middlewares\VerifyCsrfTokenMiddleware::class,
\Middlewares\AuthMiddleware::class,
];
}
2 changes: 1 addition & 1 deletion core/functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ function url(string $url = '')
}
}

function env(string $params, string $default = null)
function env(string $params, string|bool $default = null)
{
$env = Env::get($params, $default);
return $env ?? $default;
Expand Down
10 changes: 4 additions & 6 deletions core/libraries/Http/Router.php
Original file line number Diff line number Diff line change
Expand Up @@ -263,13 +263,11 @@ public static function view(string $route, string $view, array $params = [])
*/
public function dispatch()
{
if (!is_cli()) {
$this->method = strtoupper($_SERVER['REQUEST_METHOD']);
$this->uri = $this->getUri();
$this->callback = $this->getRouteCallback($this->method, $this->uri);
$this->method = strtoupper($_SERVER['REQUEST_METHOD']);
$this->uri = $this->getUri();
$this->callback = $this->getRouteCallback($this->method, $this->uri);

return $this->callback ? $this->resolve($this->callback) : $this->notRouteRegistered();
}
return $this->callback ? $this->resolve($this->callback) : $this->notRouteRegistered();
}

/**
Expand Down
84 changes: 84 additions & 0 deletions core/libraries/Middlewares/AuthMiddleware.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
<?php

declare(strict_types=1);

namespace Middlewares;

use Auth\Auth;
use RuntimeException;
use App\Middlewares\BaseMiddleware;

/**
* Middleware that verifies if a user is authenticated
* and has permissions to access the current action.
*/
class AuthMiddleware extends BaseMiddleware
{
protected array $actions;
private bool $allowedAction;
private const ALLOWED_ACTION = true;
private const NOT_ALLOWED_ACTION = false;

/**
* __construct
*
* @param mixed $actions
* @param mixed $allowedAction
* @return void
*/
public function __construct(array $actions = [], bool $allowedAction = self::NOT_ALLOWED_ACTION)
{
$this->actions = $actions;
$this->allowedAction = $allowedAction;
}

/**
* Execute the action, checking authentication and permissions.
*
* @return bool True if the action was executed successfully, false otherwise.
* @throws RuntimeException If the user does not have sufficient permissions.
*/
public function execute()
{
if (!(new Auth(app()))->check()) {
$this->validatePermission();
}
}

/**
* Validates if the user has permissions to access the current action.
*
* @throws RuntimeException If the user doesn't have permissions
* to access the current action.
*/
private function validatePermission()
{
// Get the current action from the application's controller
$action = app()->controller;
$isAllowed = ($this->allowedAction === self::NOT_ALLOWED_ACTION)
? (empty($this->actions) || in_array($action, $this->actions))
: in_array($action, $this->actions);

// Throw an exception if the user doesn't have permissions
if (!$isAllowed) {
$this->throwPermissionException();
}
}

/**
* Throws a RuntimeException indicating insufficient permissions.
* @throws RuntimeException If the user does not have sufficient permissions.
*/
private function throwPermissionException()
{
if (app()->isProduction()) {
$viewFile = config('paths.viewsErrorsPath') .
DIRECTORY_SEPARATOR . config('app.errorPages.500');

$output = app()->controller->renderView($viewFile);
die($output);
}

throw new RuntimeException('You do not have sufficient permissions to perform this operation.');
}
}
37 changes: 37 additions & 0 deletions core/libraries/Middlewares/FiberMiddleware.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php

declare(strict_types=1);

namespace Middlewares;

use Fiber;
use App\Middlewares\BaseMiddleware;

/**
* Middleware to ensure each request is processed in a separate `Fiber`
*
* The `Fiber` class has been added in PHP 8.1+, so this middleware is only used
* on PHP 8.1+. On supported PHP versions, this middleware is automatically
* added to the list of middleware handlers, so there's no need to reference
* this class in application code.
*/
class FiberMiddleware extends BaseMiddleware
{
/**
* @param callable $callback
*/
public function execute(callable $callback)
{
$fiber = new Fiber(function () use ($callback) {

$response = yield $callback();
return $response;
});

$fiber->start();

if ($fiber->isTerminated()) {
return $fiber->getReturn();
}
}
}
44 changes: 44 additions & 0 deletions core/libraries/Middlewares/MaintenanceMiddleware.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?php

declare(strict_types=1);

namespace Middlewares;

use App\Middlewares\BaseMiddleware;

/**
* Checks if the site is in maintenance mode.
*/
class MaintenanceMiddleware extends BaseMiddleware
{
/**
* Configuration property.
* @var mixed
*/
protected $config;

/**
* Checks if the user is authenticated and has
* permissions to access the current action.
*
* @return void
*/
public function execute()
{
$maintenance = env('APP_DOWN', false);
if ($maintenance === true)
return $this->showViewMaintenance();
}

/**
* Displays the maintenance view.
* @return void
*/
private function showViewMaintenance()
{
$viewFile = config('paths.viewsErrorsPath') .
DIRECTORY_SEPARATOR . config('view.errorPages.503');
$output = app()->controller->renderView($viewFile);
die($output);
}
}
108 changes: 108 additions & 0 deletions core/libraries/Middlewares/RateLimiterMiddleware.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
<?php

declare(strict_types=1);

namespace App\Middlewares;

use Fiber;
use App\Middlewares\BaseMiddleware;


/**
* Class RateLimiterMiddleware
* This class provides rate limiting functionality for API requests.
*/
class RateLimiterMiddleware extends BaseMiddleware
{
private int $maxRequestsPerSecond;
private float $lastRequestTime;
private int $maxBurstRequests = 10;
private int $burstRequestCount = 0;
private int $burstRequestsPerRequest = 1;
private int $lastBurstReset = 0;
private Fiber $rateLimiterFiber;
private array $beforeLimitingHooks;

/**
* RateLimiter constructor.
*
* @param int $maxRequestsPerSecond Maximum number of requests per second.
* @param int $burstRequestsPerRequest Number of burst requests allowed per single request.
*/
public function __construct(int $maxRequestsPerSecond, int $burstRequestsPerRequest = 1)
{
$this->maxRequestsPerSecond = $maxRequestsPerSecond;
$this->lastRequestTime = microtime(true);
$this->burstRequestsPerRequest = $burstRequestsPerRequest;

$this->rateLimiterFiber = new Fiber(function () {
while (true) {
$currentTime = microtime(true);
$timeSinceLastRequest = $currentTime - $this->lastRequestTime;

if ($timeSinceLastRequest < 1 / $this->maxRequestsPerSecond) {
usleep((int)((1 / $this->maxRequestsPerSecond - $timeSinceLastRequest) * 1000000));
}

$this->lastRequestTime = microtime(true);
Fiber::suspend();
}
});

$this->rateLimiterFiber->start();
}

/**
* Resets the burst counter if needed.
* @return void
*/
private function resetBurstIfNeeded()
{
if (time() - $this->lastBurstReset > 60) {
$this->burstRequestCount = 0;
$this->lastBurstReset = time();
}
}

/**
* Adds a hook/event before applying limiting.
*
* @param callable $beforeLimitingHook Function to execute before
* applying the limitation.
*/
public function addBeforeLimitingHook(callable $beforeLimitingHook)
{
$this->beforeLimitingHooks[] = $beforeLimitingHook;
}

/**
* Makes a request, applying the limitation and executing events/hooks.
* @param callable $apiCall API call function to be executed.
*/
public function makeRequest(callable $apiCall)
{
$this->resetBurstIfNeeded();

foreach ($this->beforeLimitingHooks as $beforeLimitingHook) {
call_user_func($beforeLimitingHook);
}

for ($i = 0; $i < $this->burstRequestsPerRequest; $i++) {
if ($this->burstRequestCount < $this->maxBurstRequests) {
$this->burstRequestCount++;
$apiCall();
} else {
$this->rateLimiterFiber->resume();
$apiCall();
}
}
}

/**
* @return void
*/
public function execute()
{
new self(10);
}
}
Loading

0 comments on commit 6e81957

Please sign in to comment.