Fix Authentication listeners

This commit is contained in:
Bubka 2024-07-03 11:14:49 +02:00
parent 57d78a8675
commit 8c89c6f0ab
7 changed files with 68 additions and 32 deletions

View File

@ -2,8 +2,11 @@
namespace App\Http\Middleware;
use App\Events\VisitedByProxyUser;
use Illuminate\Auth\Middleware\Authenticate as Middleware;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Auth;
class Authenticate extends Middleware
{
@ -17,13 +20,13 @@ class Authenticate extends Middleware
*/
protected function authenticate($request, array $guards)
{
$proxyGuard = 'reverse-proxy-guard';
if (empty($guards)) {
// Will retreive the default guard
$guards = [null];
} else {
// We replace routes guard by the reverse proxy guard if necessary
$proxyGuard = 'reverse-proxy-guard';
if (config('auth.defaults.guard') === $proxyGuard) {
$guards = [$proxyGuard];
}
@ -35,12 +38,22 @@ protected function authenticate($request, array $guards)
// We now have an authenticated user so we override the locale already set
// by the SetLanguage global middleware
$lang = $this->auth->guard()->user()->preferences['lang'];
$user = $this->auth->guard()->user();
$lang = $user->preferences['lang'];
if (in_array($lang, config('2fauth.locales')) && ! App::isLocale($lang)) {
App::setLocale($lang);
}
// Unlike the SessionGuard, the reverse-proxy-guard does not implement an attempt()
// method when it comes to log the user in. So auth events (Login, FailedLogin, etc..) are not
// fired by the guard, they are not even relevant.
// So when using the reverse-proxy-guard, we fire a VisitedByProxyUser event from here, but only
// if the user last request is older than 15 minutes to avoid too many dispatchs
if ($guard === $proxyGuard && (! $user->last_seen_at || Carbon::parse($user->last_seen_at) < Carbon::now()->subMinutes(15))) {
event(new VisitedByProxyUser($user));
}
return;
}
}

View File

@ -26,6 +26,8 @@
use App\Notifications\FailedLoginNotification;
use Illuminate\Auth\Events\Failed;
use Illuminate\Support\Facades\Log;
use TypeError;
class FailedLoginListener extends AbstractAccessListener
{
@ -35,23 +37,22 @@ class FailedLoginListener extends AbstractAccessListener
public function handle(mixed $event) : void
{
if (! $event instanceof Failed) {
return;
throw new TypeError(self::class . '::handle(): Argument #1 ($event) must be of type ' . Failed::class);
}
if ($event->user) {
/**
* @var \App\Models\User
*/
$user = $event->user;
$guard = $event->guard;
$ip = config('2fauth.proxy_headers.forIp') ?? $this->request->ip();
$user = $event->user;
$ip = config('2fauth.proxy_headers.forIp') ?? $this->request->ip();
$log = $user->authentications()->create([
'ip_address' => $ip,
'user_agent' => $this->request->userAgent(),
'login_at' => now(),
'login_successful' => false,
'guard' => $guard,
'guard' => $event->guard,
'login_method' => $this->loginMethod(),
]);
@ -59,5 +60,6 @@ public function handle(mixed $event) : void
$user->notify(new FailedLoginNotification($log));
}
}
else Log::info(sprintf('%s received an event with a null $user member. Nothing has been written to the auth log', self::class));
}
}

View File

@ -27,6 +27,7 @@
use App\Notifications\SignedInWithNewDeviceNotification;
use Illuminate\Auth\Events\Login;
use Illuminate\Support\Carbon;
use TypeError;
class LoginListener extends AbstractAccessListener
{
@ -36,7 +37,7 @@ class LoginListener extends AbstractAccessListener
public function handle(mixed $event) : void
{
if (! $event instanceof Login) {
return;
throw new TypeError(self::class . '::handle(): Argument #1 ($event) must be of type ' . Login::class);
}
/**
@ -45,16 +46,21 @@ public function handle(mixed $event) : void
$user = $event->user;
$ip = config('2fauth.proxy_headers.forIp') ?? $this->request->ip();
$userAgent = $this->request->userAgent();
$known = $user->authentications()->whereIpAddress($ip)->whereUserAgent($userAgent)->whereLoginSuccessful(true)->first();
$known = $user->authentications()
->whereIpAddress($ip)
->whereUserAgent($userAgent)
->whereLoginSuccessful(true)
->whereGuard($event->guard)
->whereLoginMethod($this->loginMethod())
->first();
$newUser = Carbon::parse($user->{$user->getCreatedAtColumn()})->diffInMinutes(Carbon::now(), true) < 1;
$guard = $event->guard;
$log = $user->authentications()->create([
'ip_address' => $ip,
'user_agent' => $userAgent,
'login_at' => now(),
'login_successful' => true,
'guard' => $guard,
'guard' => $event->guard,
'login_method' => $this->loginMethod(),
]);

View File

@ -26,6 +26,7 @@
use App\Models\AuthLog;
use Illuminate\Auth\Events\Logout;
use TypeError;
class LogoutListener extends AbstractAccessListener
{
@ -34,8 +35,8 @@ class LogoutListener extends AbstractAccessListener
*/
public function handle(mixed $event) : void
{
if (! $event instanceof Logout || $event->user == null) {
return;
if (! $event instanceof Logout) {
throw new TypeError(self::class . '::handle(): Argument #1 ($event) must be of type ' . Logout::class);
}
/**
@ -44,14 +45,20 @@ public function handle(mixed $event) : void
$user = $event->user;
$ip = config('2fauth.proxy_headers.forIp') ?? $this->request->ip();
$userAgent = $this->request->userAgent();
$log = $user->authentications()->whereIpAddress($ip)->whereUserAgent($userAgent)->whereGuard($event->guard)->orderByDesc('login_at')->first();
$guard = $event->guard;
$log = $user->authentications()
->whereIpAddress($ip)
->whereUserAgent($userAgent)
->whereGuard($event->guard)
->whereLoginMethod($this->loginMethod())
->orderByDesc('login_at')
->first();
if (! $log) {
$log = new AuthLog([
'ip_address' => $ip,
'user_agent' => $userAgent,
'guard' => $guard,
'ip_address' => $ip,
'user_agent' => $userAgent,
'guard' => $event->guard,
'login_method' => $this->loginMethod(),
]);
}

View File

@ -26,6 +26,7 @@
use App\Models\AuthLog;
use Illuminate\Auth\Events\OtherDeviceLogout;
use TypeError;
class OtherDeviceLogoutListener extends AbstractAccessListener
{
@ -35,7 +36,7 @@ class OtherDeviceLogoutListener extends AbstractAccessListener
public function handle(mixed $event) : void
{
if (! $event instanceof OtherDeviceLogout) {
return;
throw new TypeError(self::class . '::handle(): Argument #1 ($event) must be of type ' . OtherDeviceLogout::class);
}
/**
@ -44,7 +45,11 @@ public function handle(mixed $event) : void
$user = $event->user;
$ip = config('2fauth.proxy_headers.forIp') ?? $this->request->ip();
$userAgent = $this->request->userAgent();
$authLog = $user->authentications()->whereIpAddress($ip)->whereUserAgent($userAgent)->first();
$authLog = $user->authentications()
->whereIpAddress($ip)
->whereUserAgent($userAgent)
->whereLoginMethod($this->loginMethod())
->first();
$guard = $event->guard;
if (! $authLog) {

View File

@ -6,6 +6,8 @@
use App\Extensions\RemoteUserProvider;
use App\Notifications\SignedInWithNewDeviceNotification;
use Illuminate\Support\Carbon;
use Illuminate\Support\Str;
use TypeError;
class VisitedByProxyUserListener extends AbstractAccessListener
{
@ -15,7 +17,7 @@ class VisitedByProxyUserListener extends AbstractAccessListener
public function handle(mixed $event) : void
{
if (! $event instanceof VisitedByProxyUser) {
return;
throw new TypeError(self::class . '::handle(): Argument #1 ($event) must be of type ' . VisitedByProxyUser::class);
}
/**
@ -24,9 +26,14 @@ public function handle(mixed $event) : void
$user = $event->user;
$ip = config('2fauth.proxy_headers.forIp') ?? $this->request->ip();
$userAgent = $this->request->userAgent();
$known = $user->authentications()->whereIpAddress($ip)->whereUserAgent($userAgent)->whereLoginSuccessful(true)->first();
$newUser = Carbon::parse($user->{$user->getCreatedAtColumn()})->diffInMinutes(Carbon::now(), true) < 1;
$guard = config('auth.defaults.guard');
$known = $user->authentications()
->whereIpAddress($ip)
->whereUserAgent($userAgent)
->whereLoginSuccessful(true)
->whereGuard($guard)
->first();
$newUser = Carbon::parse($user->{$user->getCreatedAtColumn()})->diffInMinutes(Carbon::now(), true) < 1;
$log = $user->authentications()->create([
'ip_address' => $ip,
@ -34,9 +41,10 @@ public function handle(mixed $event) : void
'login_at' => now(),
'login_successful' => true,
'guard' => $guard,
'login_method' => null,
]);
if (! $known && ! $newUser && ! str_ends_with($user->email, RemoteUserProvider::FAKE_REMOTE_DOMAIN) && $user->preferences['notifyOnNewAuthDevice']) {
if (! $known && ! $newUser && Str::endsWith($user->email, RemoteUserProvider::FAKE_REMOTE_DOMAIN) && $user->preferences['notifyOnNewAuthDevice']) {
$user->notify(new SignedInWithNewDeviceNotification($log));
}
}

View File

@ -9,6 +9,7 @@
use Illuminate\Auth\GuardHelpers;
use Illuminate\Contracts\Auth\Guard;
use Illuminate\Contracts\Auth\UserProvider;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Log;
class ReverseProxyGuard implements Guard
@ -75,13 +76,7 @@ public function user()
}
}
if ($this->user = $this->provider->retrieveById($identifier)) {
if ($this->user->lastLoginAt() < now()->subMinutes(15)) {
event(new VisitedByProxyUser($this->user));
}
}
return $this->user;
return $this->user = $this->provider->retrieveById($identifier);
}
/**