Skip to content

Latest commit

 

History

History
278 lines (230 loc) · 8.9 KB

File metadata and controls

278 lines (230 loc) · 8.9 KB

Should block request

In order to enable the user blocking and rate limiting features, the protected app can call \aikido\should_block_request to obtain the blocking decision for the current request and act accordingly. We provide middleware examples that can be used in different scenarious. Make sure to add this middleware as early as possible in the request handling process, but after the authentication middleware, so that the user information is available.

No framework

<?php

namespace App\Middleware;

use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Laminas\Diactoros\Response; // Or use any other PSR-7 implementation

class AikidoMiddleware implements MiddlewareInterface
{
    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
    {
        // Start the session (if needed) to track user login status
        session_start();

        // Check if Aikido extension is loaded
        if (!extension_loaded('aikido')) {
            // Extension not loaded
            // Pass the request to the next middleware or request handler
            return $handler->handle($request);
        }

        // You can pass in the Aikido token here
        // \aikido\set_token("your token here");

        // Get the user ID / name (from session or other auth system)
        $userId = $this->getAuthenticatedUserId();

        // If the user is authenticated, set the user ID in Aikido Zen context
        if ($userId) {
            // Username is optional: \aikido\set_user can be called only with user ID
            $userName = $this->getAuthenticatedUserName();
            \aikido\set_user($userId, $userName);
        }

        // Check blocking decision from Aikido
        $decision = \aikido\should_block_request();

        if (!$decision->block) {
            // Aikido decided not to block
            // Pass the request to the next middleware or request handler
            return $handler->handle($request);
        }

        if ($decision->type == "blocked") {
            // If the user/ip is blocked, return a 403 status code
            $message = "";
            if ($decision->trigger == "user") {
                $message = "Your user is blocked!";
            }

            return new Response([
                'message' => $message,
            ], 403);
        }
        else if ($decision->type == "ratelimited") {
            // If the rate limit is exceeded, return a 429 status code
            $message = "";
            if ($decision->trigger == "user") {
                $message = "Your user exceeded the rate limit for this endpoint!";
            }
            else if ($decision->trigger == "ip") {
                $message = "Your IP ({$decision->ip}) exceeded the rate limit for this endpoint!";
            }
            else if ($decision->trigger == "group") {
                $message = "Your group exceeded the rate limit for this endpoint!";
            }
            return new Response([
                'message' => $message,
            ], 429);
        }

        // Aikido decided to block but decision type is not implemented
        return new Response([
            'message' => 'Something went wrong!',
        ], 500);
    }

    // Example function to simulate user authentication
    private function getAuthenticatedUserId(): ?int
    {
        return $_SESSION['user_id'] ?? null;
    }
    // Example function to simulate user authentication
    private function getAuthenticatedUserName(): ?string
    {
        return $_SESSION['user_name'] ?? null;
    }
}

Laravel

  1. Place the AikidoMiddleware in app/Http/Middleware/AikidoMiddleware.php.
<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Support\Facades\Auth;

class AikidoMiddleware
{
    public function handle($request, Closure $next)
    {
        // Check if Aikido extension is loaded
        if (!extension_loaded('aikido')) {
            return $next($request);
        }

        // You can pass in the Aikido token here
        // \aikido\set_token("your token here");


        // Get the authenticated user's ID from Laravel's Auth system
        $userId = Auth::id();

        // If a user is authenticated, set the user in Aikido Zen context
        if ($userId) {
            \aikido\set_user($userId);
            // If you want to set the user's name in Aikido Zen context, you can change the above to:
            // \aikido\set_user($userId, Auth::user()?->name);
        }

        // Check blocking decision from Aikido
        $decision = \aikido\should_block_request();

        if ($decision->block) {
            if ($decision->type == "blocked") {
                if ($decision->trigger == "user") {
                    return response('Your user is blocked!', 403);
                }
            }
            else if ($decision->type == "ratelimited") {
                if ($decision->trigger == "user") {
                    return response('Your user exceeded the rate limit for this endpoint!', 429);
                }
                else if ($decision->trigger == "ip") {
                    return response("Your IP ({$decision->ip}) exceeded the rate limit for this endpoint!", 429);
                }
                else if ($decision->trigger == "group") {
                    return response("Your group exceeded the rate limit for this endpoint!", 429);
                }
            }
        }

        // Continue to the next middleware or request handler
        return $next($request);
    }
}
  1. In bootstrap/app.php, apply the following changes:
<?php
// ...

use App\Http\Middleware\AikidoMiddleware;

return Application::configure(basePath: dirname(__DIR__))
    ->withRouting(
        web: __DIR__.'/../routes/web.php',
        commands: __DIR__.'/../routes/console.php',
        health: '/up',
    )
    ->withMiddleware(function (Middleware $middleware): void {
        $middleware->web(append: [
            AikidoMiddleware::class,
        ]);
        // Append AikidoMiddleware to other groups ('api' for example)
    })

// ...

Symfony

  1. Create an EventSubscriber in src/EventSubscriber/AikidoEventSubscriber.php:
<?php

namespace App\EventSubscriber;

use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Bundle\SecurityBundle\Security;

class AikidoEventSubscriber implements EventSubscriberInterface
{
    public function __construct(
        private readonly Security $security
    ) {
    }

    public static function getSubscribedEvents(): array
    {
        return [
            KernelEvents::REQUEST => ['onKernelRequest', 10],
        ];
    }

    public function onKernelRequest(RequestEvent $event): void
    {
        // Only handle the main request
        if (!$event->isMainRequest()) {
            return;
        }

        // Check if Aikido extension is loaded
        if (!extension_loaded('aikido')) {
            return;
        }

        // You can pass in the Aikido token here
        // \aikido\set_token("your token here");

        // Get the authenticated user from Symfony's Security component
        $user = $this->security->getUser();

        // If a user is authenticated, set the user in Aikido Zen context
        if ($user) {
            $userId = $user->getUserIdentifier();
            \aikido\set_user($userId);
            // If you want to set the user's name in Aikido Zen context, you can change the above to:
            // \aikido\set_user($userId, $user->getUsername());
        }

        // Check blocking decision from Aikido
        $decision = \aikido\should_block_request();

        if ($decision->block) {
            if ($decision->type == "blocked") {
                if ($decision->trigger == "user") {
                    $event->setResponse(new JsonResponse(
                        ['message' => 'Your user is blocked!'],
                        403
                    ));
                    return;
                }
            }
            else if ($decision->type == "ratelimited") {
                $message = '';
                if ($decision->trigger == "user") {
                    $message = 'Your user exceeded the rate limit for this endpoint!';
                }
                else if ($decision->trigger == "ip") {
                    $message = "Your IP ({$decision->ip}) exceeded the rate limit for this endpoint!";
                }
                else if ($decision->trigger == "group") {
                    $message = "Your group exceeded the rate limit for this endpoint!";
                }

                $event->setResponse(new JsonResponse(
                    ['message' => $message],
                    429
                ));
                return;
            }
        }
    }
}