wobqqq/nova-aegis-smart-ip-blocker
A Laravel Nova package for smart IP blocking that helps protect Laravel apps by detecting suspicious or abusive requests, automatically blocking offending IPs, and letting admins manage blocked addresses, whitelists, and security events from the Nova panel.
Start by installing the package and publishing its assets:
composer require <vendor>/smart-ip-blocker
php artisan vendor:publish --tag="smart-ip-blocker-config"
php artisan vendor:publish --tag="smart-ip-blocker-migrations"
php artisan migrate
Look first at config/smart-ip-blocker.php — this is where the detection window, thresholds, and storage driver live. Then inspect the visits table migration and the Visit model, because that model is the core domain object: every request the middleware observes is recorded as a Visit, and the blocker reasons over those records rather than over the raw request.
Your first use case is almost always protecting authentication and form endpoints from credential-stuffing or scraping. Register the middleware on the routes you care about:
Route::middleware('smart-ip-blocker')->group(function () {
Route::post('/login', [LoginController::class, 'store']);
Route::post('/register', [RegisterController::class, 'store']);
});
Once that is in place, hit a protected route a few times and inspect the visits table. If rows appear and the response flips to a block after your configured threshold, the pipeline is wired correctly.
Middleware-first, action-second. The primary integration is the HTTP middleware, but the underlying SmartIpBlocker is a resolvable service. Inject it when you need to make blocking decisions outside the request lifecycle (console commands, jobs, custom guards):
public function handle(SmartIpBlocker $blocker, Visit $visit)
{
if ($blocker->shouldBlock($visit)) {
// custom response
}
}
Note the v1.2.0 signature: the blocker consumes a Visit rather than the request. If you previously resolved the blocker and passed $request, construct or fetch a Visit for the IP you are evaluating instead.
Time-aware logic via the PSR clock. The blocker reads "now" from a PSR-20 ClockInterface, not from now(). This is the cleanest extension point in the package. In tests, bind a frozen clock so window-based rules are deterministic:
$this->app->instance(ClockInterface::class, new FrozenClock(new DateTimeImmutable('2024-01-01 12:00:00')));
In production the package ships a system clock binding, so you normally do nothing — but if your app already binds ClockInterface, the blocker will honour it.
Disabling programmatically. The disable path is encapsulated in the DisableSmartIpBlocker action. Call it directly from an admin controller or a test rather than manipulating config at runtime:
app(DisableSmartIpBlocker::class)->execute();
The bundled artisan command is a thin wrapper around this action. If you need to disable blocking for a support session, invoke the command; if you need to disable it in a test, invoke the action. Keeping that distinction avoids stale config caches.
Thresholds and windows in config. Tune the number of allowed visits and the lookback period in config rather than in code. Because the blocker queries the visits table, keep an index on (ip, created_at) (the published migration does this) and consider pruning old rows on a schedule:
Schedule::command('model:prune', ['--model' => [Visit::class]])->daily();
Whitelisting. For internal services, health checks, or your own office IPs, prefer an allow-list middleware that runs before the blocker rather than early-returning inside your controllers. This keeps the blocking decision in one place.
Visit and the disable command delegates to DisableSmartIpBlocker. Applications that only use the middleware and config are unaffected. If you extended SmartIpBlocker, overrode the command, or resolved the blocker manually with a request argument, update those call sites.TrustProxies correctly or every visit will share the proxy's IP and your thresholds will trip for everyone.ClockInterface for testing and forget to restore it, other time-sensitive code in the same test suite may see frozen time. Bind it in the test that needs it, not in a base test case shared across suites.php artisan migrate after install produces an empty visits table and the blocker will silently never trigger — there is nothing to count. Watch for this in CI.php artisan config:cache as part of deploy. Editing the config file on a cached app has no effect and is a common "why is it still blocking" cause.visits table grows with traffic. Without pruning, lookback queries degrade over months. Schedule pruning and monitor table size.DisableSmartIpBlocker is a separate action, assert both that blocking occurs when enabled and that the action short-circuits it. This catches regressions from the v1.2.0 command rewiring.SmartIpBlocker (decorator or subclass) rather than editing published config or forking middleware — the package's internal refactors (as in 1.2.0) are safer to absorb when your integration sits at the public service boundary.How can I help you explore Laravel packages today?