No. But you can easily create it yourself. I do so myself in a middleware. I use this package to get the location https://packagist.org/packages/stevebauman/location and then return a "forbidden" response if they are not in the whitelist (just an array in a config file)
ip block city
how to deny access to a site from countries, is there a ready solution on laravel 9?
@Sinnbeck Can you tell me in detail how to implement all this on Laravel 9, please?
@angerfist Its just a simple middleware class that I have added to the web array in /app/Http/Kernel.php
I added the countries to to a config file which reads them from an env file :)
<?php
namespace App\Http\Middleware;
use App\Http\Middleware\Traits\CheckByIp;
use Closure;
use Illuminate\Support\Facades\Log;
use Stevebauman\Location\Facades\Location;
class RestrictByCountry
{
protected $except = [
'employment/*',
'geo',
'webhooks/*',
];
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
*
* @return mixed
*/
public function handle($request, Closure $next)
{
if (!app()->environment('production') || $this->inExceptArray($request)) {
return $next($request);
}
$position = Location::get($request->ip());
$access = $this->accessByCountry($position);
if (!$access) {
abort(405, 'Not allowed');
}
return $next($request);
}
/**
* Determine if the request has a URI that should pass through CSRF verification.
*
* @param \Illuminate\Http\Request $request
*
* @return bool
*/
protected function inExceptArray($request)
{
foreach ($this->except as $except) {
if ($except !== '/') {
$except = trim($except, '/');
}
if ($request->fullUrlIs($except) || $request->is($except)) {
return true;
}
}
return false;
}
/**
* @return bool
*/
protected function accessByCountry($position): bool
{
$allowed_countries = config('auth.countries');
$country = $position->countryName;
return in_array($country, $allowed_countries);
}
}
@Sinnbeck and for example, please show the .env file
@angerfist something like
ALLOWED_COUNTRIES="Sweden Germany"
And in the auth.php config file
'countries' => env(explode(' ', 'ALLOWED_COUNTRIES'), []),
@Sinnbeck Thank you very much, I'll try it, if everything goes well I put it as the best answer
@angerfist ok. Be aware that my solution is for countries. You mentioned both countries and cities so I am unsure which you are after. But be aware that city won't be reliable
@Sinnbeck here is my kernel.php how to edit it correctly for this?
- Made composer require stevebauman/location
- php artisan vendor:publish --provider="Stevebauman\Location\LocationServiceProvider"
- I started to do as you have here, as a result, a white screen, changed the country to my own. Need your help, please help
<?php
namespace App\Http;
use Illuminate\Foundation\Http\Kernel as HttpKernel;
class Kernel extends HttpKernel
{
/**
* The application's global HTTP middleware stack.
*
* These middleware are run during every request to your application.
*
* @var array<int, class-string|string>
*/
protected $middleware = [
// \App\Http\Middleware\TrustHosts::class,
\App\Http\Middleware\TrustProxies::class,
\Illuminate\Http\Middleware\HandleCors::class,
\App\Http\Middleware\PreventRequestsDuringMaintenance::class,
\Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
\App\Http\Middleware\TrimStrings::class,
\Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
];
/**
* The application's route middleware groups.
*
* @var array<string, array<int, class-string|string>>
*/
protected $middlewareGroups = [
'web' => [
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\VerifyCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
'api' => [
// \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
'throttle:api',
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
];
/**
* The application's route middleware.
*
* These middleware may be assigned to groups or used individually.
*
* @var array<string, class-string|string>
*/
protected $routeMiddleware = [
'auth' => \App\Http\Middleware\Authenticate::class,
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
'auth.session' => \Illuminate\Session\Middleware\AuthenticateSession::class,
'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
'can' => \Illuminate\Auth\Middleware\Authorize::class,
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class,
'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class,
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
];
}
@angerfist You add the middleware to the web array
protected $middlewareGroups = [
'web' => [
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\VerifyCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
\App\Http\Middleware\RestrictByCountry::class, //here
],
'api' => [
// \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
'throttle:api',
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
];
And I don't know what you mean by a white screen. That normally assumes you are returning an empty response somewhere.
@Sinnbeck not working, 500 error
@Sinnbeck I'll try to figure it out myself
@angerfist that should give you an error message?
@Sinnbeck my config/auth.php
<?php
return [
/*
|--------------------------------------------------------------------------
| Authentication Defaults
|--------------------------------------------------------------------------
|
| This option controls the default authentication "guard" and password
| reset options for your application. You may change these defaults
| as required, but they're a perfect start for most applications.
|
*/
'defaults' => [
'guard' => 'web',
'passwords' => 'users',
],
/*
|--------------------------------------------------------------------------
| Authentication Guards
|--------------------------------------------------------------------------
|
| Next, you may define every authentication guard for your application.
| Of course, a great default configuration has been defined for you
| here which uses session storage and the Eloquent user provider.
|
| All authentication drivers have a user provider. This defines how the
| users are actually retrieved out of your database or other storage
| mechanisms used by this application to persist your user's data.
|
| Supported: "session"
|
*/
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
],
/*
|--------------------------------------------------------------------------
| User Providers
|--------------------------------------------------------------------------
|
| All authentication drivers have a user provider. This defines how the
| users are actually retrieved out of your database or other storage
| mechanisms used by this application to persist your user's data.
|
| If you have multiple user tables or models you may configure multiple
| sources which represent each model / table. These sources may then
| be assigned to any extra authentication guards you have defined.
|
| Supported: "database", "eloquent"
|
*/
'providers' => [
'users' => [
'driver' => 'eloquent',
'model' => App\Models\User::class,
],
// 'users' => [
// 'driver' => 'database',
// 'table' => 'users',
// ],
],
/*
|--------------------------------------------------------------------------
| Resetting Passwords
|--------------------------------------------------------------------------
|
| You may specify multiple password reset configurations if you have more
| than one user table or model in the application and you want to have
| separate password reset settings based on the specific user types.
|
| The expire time is the number of minutes that each reset token will be
| considered valid. This security feature keeps tokens short-lived so
| they have less time to be guessed. You may change this as needed.
|
*/
'passwords' => [
'users' => [
'provider' => 'users',
'table' => 'password_resets',
'expire' => 60,
'throttle' => 60,
],
],
/*
|--------------------------------------------------------------------------
| Password Confirmation Timeout
|--------------------------------------------------------------------------
|
| Here you may define the amount of seconds before a password confirmation
| times out and the user is prompted to re-enter their password via the
| confirmation screen. By default, the timeout lasts for three hours.
|
*/
'password_timeout' => 10800,
'countries' => env(explode(' ', 'ALLOWED_COUNTRIES'), []),
];
@angerfist and the env?
APP_NAME=Laravel
APP_ENV=production
APP_KEY=*********************
APP_DEBUG=false
APP_URL=https://site.com
LOG_CHANNEL=stack
LOG_DEPRECATIONS_CHANNEL=null
LOG_LEVEL=debug
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=****
DB_USERNAME=***
DB_PASSWORD=****
BROADCAST_DRIVER=log
CACHE_DRIVER=file
FILESYSTEM_DISK=local
QUEUE_CONNECTION=sync
SESSION_DRIVER=file
SESSION_LIFETIME=120
MEMCACHED_HOST=127.0.0.1
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379
MAIL_MAILER=smtp
MAIL_HOST=mailhog
MAIL_PORT=1025
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=null
MAIL_FROM_ADDRESS="[email protected]"
MAIL_FROM_NAME="${APP_NAME}"
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
AWS_DEFAULT_REGION=us-east-1
AWS_BUCKET=
AWS_USE_PATH_STYLE_ENDPOINT=false
PUSHER_APP_ID=
PUSHER_APP_KEY=
PUSHER_APP_SECRET=
PUSHER_APP_CLUSTER=mt1
MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"
HONEYPOT_NAME=honeypot_for_spam
ALLOWED_COUNTRIES='russia'
@angerfist try double quotes
ALLOWED_COUNTRIES="Russia"
@Sinnbeck no work
@Sinnbeck what files should i create besides this?
@angerfist ok
None. What do you think you are missing? Still getting a white screen?
@Sinnbeck Yes, I get a white screen, I can not understand what else is needed?
@angerfist nothing is needed. But remove one thing at a time again until you get an error instead of a white screen. I use this exact code and have no such errors
@Sinnbeck and what exactly to delete one by one? I don't know what to do at all
@angerfist I have you 4 changes. 3 edits to files and adding a new file. Remove those changes one by one until you are back to your site working.
@Sinnbeck no work, white screen
@angerfist Ok. Go back to before you started then. I assume you have a backup or can reset with git?
@angerfist Why have you set production in your env-file?
That should only be set in a production environment.
If you are doing changes in production.
STOPP IMMEDIATELY!!
Laravel doesn't show any errors when in production mode, you need to check the logs from the web server. They will tell you what you are doing wrong.
Nice spot by @tray2. If you set it to production locally to test it out it will of course not work.. Your local server will not get a russian IP. Also be sure that you of course set up location.php for that package. I didnt give you instructions for that, as those are already on the page for the package.. This is not a tutorial
You might want to set up this part with a russian IP if you want to test it locally https://github.com/stevebauman/location/blob/master/config/location.php#L171
@Sinnbeck I do everything right on the working site + vps/vds
@angerfist So you are changing your production site? Ouch! Did you set up that package at least? I personally use maxmind for my sites.
@Sinnbeck tell me how to do it right
@angerfist Its literally on the page? https://github.com/stevebauman/location#setting-up-maxmind-with-a-self-hosted-database-optional
Or you can obtain a user id and license key from maxmind if you prefer https://github.com/stevebauman/location/blob/master/config/location.php#L73
@Sinnbeck I have a production site, I can't use maxmind because there is no Russia there
@angerfist Then pick another provider: https://github.com/stevebauman/location#available-drivers
@Sinnbeck nothing works for me, I don’t know what to do anymore, I’ve tried everything
@angerfist If there isnt a single of those providers that can detect a Russian IP then I dont know how I can be of any help?
Did you try typing in your ip at https://ip-api.com/ to see if that can find it?
@Sinnbeck besides this package, how else can you do it?
@angerfist I just found a random russian ip and ran it through maxmind, and it came back with Russia? So I dont see why you cant use that?
besides this package, how else can you do it?
Write your own implementation from scratch, or add a custom driver to that package: https://github.com/stevebauman/location#creating-your-own-drivers
@Sinnbeck nothing comes out, I don’t know how to write a driver, I asked for help here, but it’s all in vain in general. I asked you step by step how to set everything up
@angerfist Sorry but I dont feel like writing a complete tutorial for this. Maybe I will at a later point, for my blog. But I have literally given you all the code needed to set this up. Sounds like all you need is to download the max mind database from their website and point to it. I put mine in /storage/geoip
'maxmind' => [
'web' => [
'enabled' => false,
'user_id' => env('GEOIP_MAXMIND_USER_ID'),
'license_key' => env('GEOIP_MAXMIND_LICENSE_KEY'),
'options' => [
'host' => 'geoip.maxmind.com',
],
],
'local' => [
'type' => 'city',
'path' =>storage_path('geoip/GeoLite2-City.mmdb'),
],
],
I am using the city version, but you can use the country version if you prefer. You point to that, and change the type
@angerfist FFS, if you have no clue as to what you are doing, and you are bashing @sinnbeck who is trying to help you for free. Hire a developer to help you develop what you need.
@Tray2 I do not criticize him, on the contrary, I thank him for saying what leads me on the right path
@Sinnbeck I chose ipdata.co how to set it up please tell me
@angerfist I dont use them so I dont know. But I would imagine you just set it as the driver here: https://github.com/stevebauman/location/blob/master/config/location.php#L14
And add the key in env
IPDATA_TOKEN="somekey"
@Sinnbeck right ?
/*
|--------------------------------------------------------------------------
| Driver
|--------------------------------------------------------------------------
|
| The default driver you would like to use for location retrieval.
|
*/
// 'driver' => Stevebauman\Location\Drivers\IpApi::class,
'driver' => Stevebauman\Location\Drivers\Ipdata::class,
@angerfist Close. Its IpData not Ipdata :)
@angerfist Yes you are, you want a solution for free and you are bagering him to help you, but you haven't provided a single error or any of the code you have written.
@Sinnbeck thank you so much for this, it's what one of my apps needed.
I found that this needed to be changed
'countries' => env(explode(' ', 'ALLOWED_COUNTRIES'), []),
because it was giving me this error
($name) must be of type string, array given, called in /var/www/html/vendor/laravel/framework/src/Illuminate/Support/Env.php on line 76
so I changed it to this on my side
'countries' => explode(', ', env('ALLOWED_COUNTRIES')),
Side Note
Another thing I had to change was
ALLOWED_COUNTRIES="Sweden Germany"
because I live in South Africa it includes two words so I just made the explode separator , instead of an empty space
@migsAV Ah yes that makes sense :) Great that you got it working. Never thought that it could be 2 words :p
@angerfist check out my reply and see if that helps with your error.
People like @sinnbeck really are what give this community its backbone. How often do we see people on this forum, level 1, not subscribed, no videos watched not really reading the advice they're being given but simply expecting to copy and paste a response into their application and it just work?
@jaseofspades88 thanks man. Appreciate that 😁
thanks man. Appreciate that 😁
@Sinnbeck I’m not sure that was meant as a compliment…
I think @jaseofspades88 was pointing out that @angerfist posted a vague question and then their first reply was:
tell me in detail how to implement all this on Laravel 9, please
Even though they’ve not tried anything themselves, and have so far only watched a mere 8 videos on Laracasts. Entitled, much?
So I think they were just point out that by putting so much effort into going back and forth with them, spoon-feeding them every line of code they need to write, is just enabling more of these types of posters and questions in the future.
I’m all for helping people, but I draw the line at entitled people like the OP who just want an entire solution written for them for free so they can copy and paste it, and then come back at a later date when they have another feature they want to add to their project.
@martinbean that might be so. Maybe I misunderstood. 🤷♂️ I thought being a backbone was a good thing, but English isn't my first language
But each has their own line I suppose. I do my best to help and maybe I go too far. But if people want a strict forum where an exact question is asked the first time then maybe laracasts isn't the right place. I see laracasts as a place where people help each other any way they can.
@Sinnbeck How to contact you?
@martinbean what if I told you it was simultaneously a compliment to @sinnbeck and a gripe at the people you have detailed above. One thing I love about the work the Laracasts team and a lot of the people who put their time and effort into helping others do, is give you the helping hand you need to then go and learn more, ask questions and improve
Please or to participate in this conversation.