Skip to content

Commit

Permalink
Block by country
Browse files Browse the repository at this point in the history
  • Loading branch information
mchev committed Nov 14, 2023
1 parent 82d5bfd commit 7a23141
Show file tree
Hide file tree
Showing 14 changed files with 269 additions and 99 deletions.
56 changes: 0 additions & 56 deletions .github/ISSUE_TEMPLATE/bug.yml

This file was deleted.

8 changes: 0 additions & 8 deletions .github/ISSUE_TEMPLATE/config.yml

This file was deleted.

44 changes: 35 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ You can publish the config file with:
php artisan vendor:publish --tag="banhammer-config"
```

It is possible to define the table name and the fallback_url in the `config/ban.php` file.
It is possible to define the table name and the fallback_url in the `config/banhammer.php` file.

## Usage

Expand Down Expand Up @@ -178,6 +178,32 @@ $users->bans()->whereMeta('username', 'Jane')->get();
$users->whereBansMeta('username', 'Jane')->get();
```

### Blocking Access from Specific Countries

To enhance the security of your application, you can restrict access from specific countries by enabling the country-blocking feature in the configuration file. Follow these simple steps:

1. Open your Banhammer configuration file (config/ban.php).

2. Set the 'block_by_country' configuration option to true to enable country-based blocking.

```php
'block_by_country' => true,
```

3. Specify the list of countries you want to block by adding their country codes to the 'blocked_countries' array.

```php
'blocked_countries' => ['FR', 'ES'],
```

By configuring these settings, you effectively block access to your application for users originating from the specified countries. This helps improve the security and integrity of your system by preventing unwanted traffic from regions you've identified as potential risks.

**Important Notice:**
The Banhammer package utilizes the free version of ip-api.com for geolocation data. Keep in mind that their endpoints have a rate limit of 45 HTTP requests per minute from a single IP address. If you exceed this limit, your requests will be throttled, and you may receive a 429 HTTP status code until your rate limit window is reset.

**Developer Note:**
While Banhammer currently relies on the free version of [ip-api.com](https://ip-api.com/) for geolocation data, I'm open to exploring better alternatives. If you have suggestions for a more robust or efficient solution, or if you'd like to contribute improvements, please feel free to [open an issue](https://github.com/mchev/banhammer/issues) or submit a [pull request](https://github.com/mchev/banhammer/pulls).

### Middleware
To prevent banned users from accessing certain parts of your application, simply add the `auth.banned` middleware on the concerned routes.
```php
Expand Down Expand Up @@ -256,18 +282,18 @@ composer test

Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently.

## Roadmap
## Roadmap / Todo

- [ ] Handle UUIDs and ULIDs
- [ ] More tests
- [ ] Block IP range
- [ ] Auto block IP (Rate Limiting)
- [x] Cache
- [x] Ban history (expired, not expired)
- [ ] Web page to dispay infos (ips banned, block by country enabled, etc.). Dev mode only
- [ ] Block by country feature

## Contributing

Please see [CONTRIBUTING](CONTRIBUTING.md) for details.
To encourage active collaboration, Banhammer strongly encourages pull requests, not just bug reports. Pull requests will only be reviewed when marked as "ready for review" (not in the "draft" state) and all tests for new features are passing. Lingering, non-active pull requests left in the "draft" state will be closed after a few days.

However, if you file a bug report, your issue should contain a title and a clear description of the issue. You should also include as much relevant information as possible and a code sample that demonstrates the issue. The goal of a bug report is to make it easy for yourself - and others - to replicate the bug and develop a fix.

Remember, bug reports are created in the hope that others with the same problem will be able to collaborate with you on solving it. Do not expect that the bug report will automatically see any activity or that others will jump to fix it.

## Security Vulnerabilities

Expand Down
5 changes: 3 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
{
"name": "mchev/banhammer",
"description": "BanHamer for Laravel allows you to ban any Model by key and by IP.",
"description": "Banhammer for Laravel allows you to ban any Model by key and by IP.",
"keywords": [
"mchev",
"laravel",
"bans-for-laravel",
"ban",
"bannable",
"ip"
"ip",
"country"
],
"homepage": "https://github.com/mchev/banhammer",
"license": "MIT",
Expand Down
47 changes: 40 additions & 7 deletions config/config.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,32 +7,65 @@
| Table Name
|--------------------------------------------------------------------------
|
| Change here the name of the table that will be created during the migration to use the ban system.
| Specify the name of the table created during migration for the ban system.
| This table will store information about banned users.
|
*/

'table' => 'bans',

/*
|--------------------------------------------------------------------------
| Where to redirect banned Models
| Where to Redirect Banned Users
|--------------------------------------------------------------------------
|
| Url to which the user will be redirected when he/she tries to log in after being banned.
| If not defined, the banned user will be redirected to the previous page.
| Define the URL to which users will be redirected when attempting to log in
| after being banned. If not defined, the banned user will be redirected to
| the previous page they tried to access.
|
*/

'fallback_url' => null, // null | "/oops"
'fallback_url' => null, // Examples: null (default), "/oops", "/login"

/*
|--------------------------------------------------------------------------
| 403 Message
|--------------------------------------------------------------------------
|
| The message that will be displayed if no fallback url for banned users has been defined.
| The message that will be displayed if no fallback URL is defined for banned users.
|
*/
'message' => 'You have been banned.',

'messages' => [
'user' => 'Your account has been banned.',
'ip' => 'Access from your IP address is restricted.',
'country' => 'Access from your country is restricted.',
],

/*
|--------------------------------------------------------------------------
| Block by Country
|--------------------------------------------------------------------------
|
| Determine whether to block users based on their country. This setting uses
| the value of BANHAMMER_BLOCK_BY_COUNTRY from the environment. Enabling this
| feature may result in up to 45 HTTP requests per minute with the free version
| of https://ip-api.com/.
|
*/

'block_by_country' => env('BANHAMMER_BLOCK_BY_COUNTRY', false),

/*
|--------------------------------------------------------------------------
| List of Blocked Countries
|--------------------------------------------------------------------------
|
| Specify the countries where users will be blocked if 'block_by_country' is true.
| Add country codes to the array to restrict access from those countries.
|
*/

'blocked_countries' => [], // Examples: ['US', 'CA', 'GB']

];
6 changes: 6 additions & 0 deletions src/BanhammerServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use Mchev\Banhammer\Commands\ClearBans;
use Mchev\Banhammer\Commands\DeleteExpired;
use Mchev\Banhammer\Middleware\AuthBanned;
use Mchev\Banhammer\Middleware\BlockByCountry;
use Mchev\Banhammer\Middleware\IPBanned;
use Mchev\Banhammer\Middleware\LogoutBanned;
use Mchev\Banhammer\Models\Ban;
Expand All @@ -29,6 +30,10 @@ public function boot(): void
$router->aliasMiddleware('ip.banned', IPBanned::class);
$router->aliasMiddleware('logout.banned', LogoutBanned::class);

if (config('ban.block_by_country')) {
$router->pushMiddlewareToGroup('web', BlockByCountry::class);
}

if ($this->app->runningInConsole()) {
// Publishing the config.
$this->publishes([
Expand All @@ -46,6 +51,7 @@ public function boot(): void
$schedule = $this->app->make(Schedule::class);
$schedule->command('banhammer:unban')->everyMinute();
});

}

/**
Expand Down
35 changes: 35 additions & 0 deletions src/Exceptions/BanhammerException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php

namespace Mchev\Banhammer\Exceptions;

use Exception;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\Log;
use Symfony\Component\HttpKernel\Exception\HttpException;

class BanhammerException extends HttpException
{
public function __construct($message = null, Exception $previous = null, $code = 0)
{
parent::__construct(403, $message, $previous, [], $code);
}

/**
* Report or log an exception.
*/
public function report(): void
{
Log::error("Banhammer Exception: {$this->getMessage()}");
}

/**
* Render the exception into an HTTP response.
*/
public function render(Request $request): Response
{
return (config('ban.fallback_url'))
? redirect(config('ban.fallback_url'))
: abort(403, $this->getMessage());
}
}
5 changes: 2 additions & 3 deletions src/Middleware/AuthBanned.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,15 @@
namespace Mchev\Banhammer\Middleware;

use Closure;
use Mchev\Banhammer\Exceptions\BanhammerException;
use Symfony\Component\HttpFoundation\Response;

class AuthBanned
{
public function handle($request, Closure $next): Response
{
if ($request->user() && $request->user()->isBanned()) {
return (config('ban.fallback_url'))
? redirect(config('ban.fallback_url'))
: abort(403, config('ban.message'));
throw new BanhammerException(config('ban.messages.user'));
}

return $next($request);
Expand Down
61 changes: 61 additions & 0 deletions src/Middleware/BlockByCountry.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<?php

namespace Mchev\Banhammer\Middleware;

use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use Mchev\Banhammer\Exceptions\BanhammerException;
use Mchev\Banhammer\Services\IpApiService;

class BlockByCountry
{
protected IpApiService $ipApiService;

public function __construct(IpApiService $ipApiService)
{
$this->ipApiService = $ipApiService;
}

public function handle(Request $request, Closure $next)
{
$blockedCountries = config('ban.blocked_countries');

if ($blockedCountries && ! empty($blockedCountries)) {
$ip = $request->ip();

if (! is_null($ip)) {
try {
// Get geolocation data using the IpApiService
$geolocationData = $this->ipApiService->getGeolocationData($ip);

if ($geolocationData['status'] === 'fail') {
Log::notice('Banhammer country check failure: '.$message, [
'ip' => $ip,
]);
}

if ($this->isCountryBlocked($geolocationData, $blockedCountries)) {
throw new BanhammerException(config('ban.messages.country'));
}
} catch (\Exception $e) {
Log::debug('Banhammer Exception: '.$e->getMessage(), [
'ip' => $ip,
'country' => $geolocationData['countryCode'] ?? null,
]);

// Rethrow the exception to ensure the ban is enforced
throw new BanhammerException(config('ban.messages.country'));
}
}
}

return $next($request);
}

protected function isCountryBlocked(array $geolocationData, array $blockedCountries): bool
{
return isset($geolocationData['countryCode']) &&
$this->ipApiService->isCountryBlocked($geolocationData['countryCode'], $blockedCountries);
}
}
Loading

0 comments on commit 7a23141

Please sign in to comment.