mirror of
https://github.com/Bubka/2FAuth.git
synced 2024-11-07 08:54:22 +01:00
Complete merge with refactoring for better integration
This commit is contained in:
parent
e75589526b
commit
e498350f62
@ -179,6 +179,12 @@ LOGIN_THROTTLE=5
|
||||
AUTHENTICATION_GUARD=web-guard
|
||||
|
||||
|
||||
# Authentication log retention time, in days.
|
||||
# Log entries older than that are automatically deleted.
|
||||
|
||||
AUTHENTICATION_LOG_RETENTION=365
|
||||
|
||||
|
||||
# Name of the HTTP headers sent by the reverse proxy that identifies the authenticated user at proxy level.
|
||||
# Check your proxy documentation to find out how these headers are named (i.e 'REMOTE_USER', 'REMOTE_EMAIL', etc...)
|
||||
# (only relevant when AUTHENTICATION_GUARD is set to 'reverse-proxy-guard')
|
||||
|
3
Dockerfile
vendored
3
Dockerfile
vendored
@ -186,6 +186,9 @@ ENV \
|
||||
# authentication checks. That means your proxy is fully responsible of the authentication process, 2FAuth will
|
||||
# trust him as long as headers are presents.
|
||||
AUTHENTICATION_GUARD=web-guard \
|
||||
# Authentication log retention time, in days.
|
||||
# Log entries older than that are automatically deleted.
|
||||
AUTHENTICATION_LOG_RETENTION=365 \
|
||||
# Name of the HTTP headers sent by the reverse proxy that identifies the authenticated user at proxy level.
|
||||
# Check your proxy documentation to find out how these headers are named (i.e 'REMOTE_USER', 'REMOTE_EMAIL', etc...)
|
||||
# (only relevant when AUTHENTICATION_GUARD is set to 'reverse-proxy-guard')
|
||||
|
442
_ide_helper.php
442
_ide_helper.php
@ -5,7 +5,7 @@
|
||||
|
||||
/**
|
||||
* A helper file for Laravel, to provide autocomplete information to your IDE
|
||||
* Generated for Laravel 10.48.4.
|
||||
* Generated for Laravel 10.48.8.
|
||||
*
|
||||
* This file should not be included in your code, only analyzed by your IDE!
|
||||
*
|
||||
@ -1640,36 +1640,6 @@
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @method static bool attempt(array $credentials = [], bool $remember = false)
|
||||
* @method static bool once(array $credentials = [])
|
||||
* @method static void login(\Illuminate\Contracts\Auth\Authenticatable $user, bool $remember = false)
|
||||
* @method static \Illuminate\Contracts\Auth\Authenticatable|bool loginUsingId(mixed $id, bool $remember = false)
|
||||
* @method static \Illuminate\Contracts\Auth\Authenticatable|bool onceUsingId(mixed $id)
|
||||
* @method static bool viaRemember()
|
||||
* @method static void logout()
|
||||
* @method static \Symfony\Component\HttpFoundation\Response|null basic(string $field = 'email', array $extraConditions = [])
|
||||
* @method static \Symfony\Component\HttpFoundation\Response|null onceBasic(string $field = 'email', array $extraConditions = [])
|
||||
* @method static bool attemptWhen(array $credentials = [], array|callable|null $callbacks = null, bool $remember = false)
|
||||
* @method static void logoutCurrentDevice()
|
||||
* @method static \Illuminate\Contracts\Auth\Authenticatable|null logoutOtherDevices(string $password, string $attribute = 'password')
|
||||
* @method static void attempting(mixed $callback)
|
||||
* @method static \Illuminate\Contracts\Auth\Authenticatable getLastAttempted()
|
||||
* @method static string getName()
|
||||
* @method static string getRecallerName()
|
||||
* @method static \Illuminate\Auth\SessionGuard setRememberDuration(int $minutes)
|
||||
* @method static \Illuminate\Contracts\Cookie\QueueingFactory getCookieJar()
|
||||
* @method static void setCookieJar(\Illuminate\Contracts\Cookie\QueueingFactory $cookie)
|
||||
* @method static \Illuminate\Contracts\Events\Dispatcher getDispatcher()
|
||||
* @method static void setDispatcher(\Illuminate\Contracts\Events\Dispatcher $events)
|
||||
* @method static \Illuminate\Contracts\Session\Session getSession()
|
||||
* @method static \Illuminate\Contracts\Auth\Authenticatable|null getUser()
|
||||
* @method static \Symfony\Component\HttpFoundation\Request getRequest()
|
||||
* @method static \Illuminate\Auth\SessionGuard setRequest(\Symfony\Component\HttpFoundation\Request $request)
|
||||
* @method static \Illuminate\Support\Timebox getTimebox()
|
||||
* @method static void macro(string $name, object|callable $macro)
|
||||
* @method static void mixin(object $mixin, bool $replace = true)
|
||||
* @method static bool hasMacro(string $name)
|
||||
* @method static void flushMacros()
|
||||
* @see \Illuminate\Auth\AuthManager
|
||||
* @see \Illuminate\Auth\SessionGuard
|
||||
*/ class Auth {
|
||||
@ -1851,24 +1821,336 @@
|
||||
return $instance->getDefaultUserProvider();
|
||||
}
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* Get the currently authenticated user.
|
||||
*
|
||||
* @return \App\Models\User|null
|
||||
* @static
|
||||
*/ public static function user()
|
||||
{
|
||||
/** @var \App\Services\Auth\ReverseProxyGuard $instance */
|
||||
/** @var \Illuminate\Auth\SessionGuard $instance */
|
||||
return $instance->user();
|
||||
}
|
||||
/**
|
||||
* Get the ID for the currently authenticated user.
|
||||
*
|
||||
* @return int|string|null
|
||||
* @static
|
||||
*/ public static function id()
|
||||
{
|
||||
/** @var \Illuminate\Auth\SessionGuard $instance */
|
||||
return $instance->id();
|
||||
}
|
||||
/**
|
||||
* Log a user into the application without sessions or cookies.
|
||||
*
|
||||
* @param array $credentials
|
||||
* @return bool
|
||||
* @static
|
||||
*/ public static function once($credentials = [])
|
||||
{
|
||||
/** @var \Illuminate\Auth\SessionGuard $instance */
|
||||
return $instance->once($credentials);
|
||||
}
|
||||
/**
|
||||
* Log the given user ID into the application without sessions or cookies.
|
||||
*
|
||||
* @param mixed $id
|
||||
* @return \App\Models\User|false
|
||||
* @static
|
||||
*/ public static function onceUsingId($id)
|
||||
{
|
||||
/** @var \Illuminate\Auth\SessionGuard $instance */
|
||||
return $instance->onceUsingId($id);
|
||||
}
|
||||
/**
|
||||
* Validate a user's credentials.
|
||||
*
|
||||
* @param array $credentials
|
||||
* @return bool
|
||||
* @codeCoverageIgnore
|
||||
* @static
|
||||
*/ public static function validate($credentials = [])
|
||||
{
|
||||
/** @var \App\Services\Auth\ReverseProxyGuard $instance */
|
||||
/** @var \Illuminate\Auth\SessionGuard $instance */
|
||||
return $instance->validate($credentials);
|
||||
}
|
||||
/**
|
||||
* Attempt to authenticate using HTTP Basic Auth.
|
||||
*
|
||||
* @param string $field
|
||||
* @param array $extraConditions
|
||||
* @return \Symfony\Component\HttpFoundation\Response|null
|
||||
* @throws \Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException
|
||||
* @static
|
||||
*/ public static function basic($field = 'email', $extraConditions = [])
|
||||
{
|
||||
/** @var \Illuminate\Auth\SessionGuard $instance */
|
||||
return $instance->basic($field, $extraConditions);
|
||||
}
|
||||
/**
|
||||
* Perform a stateless HTTP Basic login attempt.
|
||||
*
|
||||
* @param string $field
|
||||
* @param array $extraConditions
|
||||
* @return \Symfony\Component\HttpFoundation\Response|null
|
||||
* @throws \Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException
|
||||
* @static
|
||||
*/ public static function onceBasic($field = 'email', $extraConditions = [])
|
||||
{
|
||||
/** @var \Illuminate\Auth\SessionGuard $instance */
|
||||
return $instance->onceBasic($field, $extraConditions);
|
||||
}
|
||||
/**
|
||||
* Attempt to authenticate a user using the given credentials.
|
||||
*
|
||||
* @param array $credentials
|
||||
* @param bool $remember
|
||||
* @return bool
|
||||
* @static
|
||||
*/ public static function attempt($credentials = [], $remember = false)
|
||||
{
|
||||
/** @var \Illuminate\Auth\SessionGuard $instance */
|
||||
return $instance->attempt($credentials, $remember);
|
||||
}
|
||||
/**
|
||||
* Attempt to authenticate a user with credentials and additional callbacks.
|
||||
*
|
||||
* @param array $credentials
|
||||
* @param array|callable|null $callbacks
|
||||
* @param bool $remember
|
||||
* @return bool
|
||||
* @static
|
||||
*/ public static function attemptWhen($credentials = [], $callbacks = null, $remember = false)
|
||||
{
|
||||
/** @var \Illuminate\Auth\SessionGuard $instance */
|
||||
return $instance->attemptWhen($credentials, $callbacks, $remember);
|
||||
}
|
||||
/**
|
||||
* Log the given user ID into the application.
|
||||
*
|
||||
* @param mixed $id
|
||||
* @param bool $remember
|
||||
* @return \App\Models\User|false
|
||||
* @static
|
||||
*/ public static function loginUsingId($id, $remember = false)
|
||||
{
|
||||
/** @var \Illuminate\Auth\SessionGuard $instance */
|
||||
return $instance->loginUsingId($id, $remember);
|
||||
}
|
||||
/**
|
||||
* Log a user into the application.
|
||||
*
|
||||
* @param \Illuminate\Contracts\Auth\Authenticatable $user
|
||||
* @param bool $remember
|
||||
* @return void
|
||||
* @static
|
||||
*/ public static function login($user, $remember = false)
|
||||
{
|
||||
/** @var \Illuminate\Auth\SessionGuard $instance */
|
||||
$instance->login($user, $remember);
|
||||
}
|
||||
/**
|
||||
* Log the user out of the application.
|
||||
*
|
||||
* @return void
|
||||
* @static
|
||||
*/ public static function logout()
|
||||
{
|
||||
/** @var \Illuminate\Auth\SessionGuard $instance */
|
||||
$instance->logout();
|
||||
}
|
||||
/**
|
||||
* Log the user out of the application on their current device only.
|
||||
*
|
||||
* This method does not cycle the "remember" token.
|
||||
*
|
||||
* @return void
|
||||
* @static
|
||||
*/ public static function logoutCurrentDevice()
|
||||
{
|
||||
/** @var \Illuminate\Auth\SessionGuard $instance */
|
||||
$instance->logoutCurrentDevice();
|
||||
}
|
||||
/**
|
||||
* Invalidate other sessions for the current user.
|
||||
*
|
||||
* The application must be using the AuthenticateSession middleware.
|
||||
*
|
||||
* @param string $password
|
||||
* @param string $attribute
|
||||
* @return \App\Models\User|null
|
||||
* @throws \Illuminate\Auth\AuthenticationException
|
||||
* @static
|
||||
*/ public static function logoutOtherDevices($password, $attribute = 'password')
|
||||
{
|
||||
/** @var \Illuminate\Auth\SessionGuard $instance */
|
||||
return $instance->logoutOtherDevices($password, $attribute);
|
||||
}
|
||||
/**
|
||||
* Register an authentication attempt event listener.
|
||||
*
|
||||
* @param mixed $callback
|
||||
* @return void
|
||||
* @static
|
||||
*/ public static function attempting($callback)
|
||||
{
|
||||
/** @var \Illuminate\Auth\SessionGuard $instance */
|
||||
$instance->attempting($callback);
|
||||
}
|
||||
/**
|
||||
* Get the last user we attempted to authenticate.
|
||||
*
|
||||
* @return \App\Models\User
|
||||
* @static
|
||||
*/ public static function getLastAttempted()
|
||||
{
|
||||
/** @var \Illuminate\Auth\SessionGuard $instance */
|
||||
return $instance->getLastAttempted();
|
||||
}
|
||||
/**
|
||||
* Get a unique identifier for the auth session value.
|
||||
*
|
||||
* @return string
|
||||
* @static
|
||||
*/ public static function getName()
|
||||
{
|
||||
/** @var \Illuminate\Auth\SessionGuard $instance */
|
||||
return $instance->getName();
|
||||
}
|
||||
/**
|
||||
* Get the name of the cookie used to store the "recaller".
|
||||
*
|
||||
* @return string
|
||||
* @static
|
||||
*/ public static function getRecallerName()
|
||||
{
|
||||
/** @var \Illuminate\Auth\SessionGuard $instance */
|
||||
return $instance->getRecallerName();
|
||||
}
|
||||
/**
|
||||
* Determine if the user was authenticated via "remember me" cookie.
|
||||
*
|
||||
* @return bool
|
||||
* @static
|
||||
*/ public static function viaRemember()
|
||||
{
|
||||
/** @var \Illuminate\Auth\SessionGuard $instance */
|
||||
return $instance->viaRemember();
|
||||
}
|
||||
/**
|
||||
* Set the number of minutes the remember me cookie should be valid for.
|
||||
*
|
||||
* @param int $minutes
|
||||
* @return \Illuminate\Auth\SessionGuard
|
||||
* @static
|
||||
*/ public static function setRememberDuration($minutes)
|
||||
{
|
||||
/** @var \Illuminate\Auth\SessionGuard $instance */
|
||||
return $instance->setRememberDuration($minutes);
|
||||
}
|
||||
/**
|
||||
* Get the cookie creator instance used by the guard.
|
||||
*
|
||||
* @return \Illuminate\Contracts\Cookie\QueueingFactory
|
||||
* @throws \RuntimeException
|
||||
* @static
|
||||
*/ public static function getCookieJar()
|
||||
{
|
||||
/** @var \Illuminate\Auth\SessionGuard $instance */
|
||||
return $instance->getCookieJar();
|
||||
}
|
||||
/**
|
||||
* Set the cookie creator instance used by the guard.
|
||||
*
|
||||
* @param \Illuminate\Contracts\Cookie\QueueingFactory $cookie
|
||||
* @return void
|
||||
* @static
|
||||
*/ public static function setCookieJar($cookie)
|
||||
{
|
||||
/** @var \Illuminate\Auth\SessionGuard $instance */
|
||||
$instance->setCookieJar($cookie);
|
||||
}
|
||||
/**
|
||||
* Get the event dispatcher instance.
|
||||
*
|
||||
* @return \Illuminate\Contracts\Events\Dispatcher
|
||||
* @static
|
||||
*/ public static function getDispatcher()
|
||||
{
|
||||
/** @var \Illuminate\Auth\SessionGuard $instance */
|
||||
return $instance->getDispatcher();
|
||||
}
|
||||
/**
|
||||
* Set the event dispatcher instance.
|
||||
*
|
||||
* @param \Illuminate\Contracts\Events\Dispatcher $events
|
||||
* @return void
|
||||
* @static
|
||||
*/ public static function setDispatcher($events)
|
||||
{
|
||||
/** @var \Illuminate\Auth\SessionGuard $instance */
|
||||
$instance->setDispatcher($events);
|
||||
}
|
||||
/**
|
||||
* Get the session store used by the guard.
|
||||
*
|
||||
* @return \Illuminate\Contracts\Session\Session
|
||||
* @static
|
||||
*/ public static function getSession()
|
||||
{
|
||||
/** @var \Illuminate\Auth\SessionGuard $instance */
|
||||
return $instance->getSession();
|
||||
}
|
||||
/**
|
||||
* Return the currently cached user.
|
||||
*
|
||||
* @return \App\Models\User|null
|
||||
* @static
|
||||
*/ public static function getUser()
|
||||
{
|
||||
/** @var \Illuminate\Auth\SessionGuard $instance */
|
||||
return $instance->getUser();
|
||||
}
|
||||
/**
|
||||
* Set the current user.
|
||||
*
|
||||
* @param \Illuminate\Contracts\Auth\Authenticatable $user
|
||||
* @return \Illuminate\Auth\SessionGuard
|
||||
* @static
|
||||
*/ public static function setUser($user)
|
||||
{
|
||||
/** @var \Illuminate\Auth\SessionGuard $instance */
|
||||
return $instance->setUser($user);
|
||||
}
|
||||
/**
|
||||
* Get the current request instance.
|
||||
*
|
||||
* @return \Symfony\Component\HttpFoundation\Request
|
||||
* @static
|
||||
*/ public static function getRequest()
|
||||
{
|
||||
/** @var \Illuminate\Auth\SessionGuard $instance */
|
||||
return $instance->getRequest();
|
||||
}
|
||||
/**
|
||||
* Set the current request instance.
|
||||
*
|
||||
* @param \Symfony\Component\HttpFoundation\Request $request
|
||||
* @return \Illuminate\Auth\SessionGuard
|
||||
* @static
|
||||
*/ public static function setRequest($request)
|
||||
{
|
||||
/** @var \Illuminate\Auth\SessionGuard $instance */
|
||||
return $instance->setRequest($request);
|
||||
}
|
||||
/**
|
||||
* Get the timebox instance used by the guard.
|
||||
*
|
||||
* @return \Illuminate\Support\Timebox
|
||||
* @static
|
||||
*/ public static function getTimebox()
|
||||
{
|
||||
/** @var \Illuminate\Auth\SessionGuard $instance */
|
||||
return $instance->getTimebox();
|
||||
}
|
||||
/**
|
||||
* Determine if the current user is authenticated. If not, throw an exception.
|
||||
@ -1878,7 +2160,7 @@
|
||||
* @static
|
||||
*/ public static function authenticate()
|
||||
{
|
||||
/** @var \App\Services\Auth\ReverseProxyGuard $instance */
|
||||
/** @var \Illuminate\Auth\SessionGuard $instance */
|
||||
return $instance->authenticate();
|
||||
}
|
||||
/**
|
||||
@ -1888,7 +2170,7 @@
|
||||
* @static
|
||||
*/ public static function hasUser()
|
||||
{
|
||||
/** @var \App\Services\Auth\ReverseProxyGuard $instance */
|
||||
/** @var \Illuminate\Auth\SessionGuard $instance */
|
||||
return $instance->hasUser();
|
||||
}
|
||||
/**
|
||||
@ -1898,7 +2180,7 @@
|
||||
* @static
|
||||
*/ public static function check()
|
||||
{
|
||||
/** @var \App\Services\Auth\ReverseProxyGuard $instance */
|
||||
/** @var \Illuminate\Auth\SessionGuard $instance */
|
||||
return $instance->check();
|
||||
}
|
||||
/**
|
||||
@ -1908,38 +2190,17 @@
|
||||
* @static
|
||||
*/ public static function guest()
|
||||
{
|
||||
/** @var \App\Services\Auth\ReverseProxyGuard $instance */
|
||||
/** @var \Illuminate\Auth\SessionGuard $instance */
|
||||
return $instance->guest();
|
||||
}
|
||||
/**
|
||||
* Get the ID for the currently authenticated user.
|
||||
*
|
||||
* @return int|string|null
|
||||
* @static
|
||||
*/ public static function id()
|
||||
{
|
||||
/** @var \App\Services\Auth\ReverseProxyGuard $instance */
|
||||
return $instance->id();
|
||||
}
|
||||
/**
|
||||
* Set the current user.
|
||||
*
|
||||
* @param \Illuminate\Contracts\Auth\Authenticatable $user
|
||||
* @return \App\Services\Auth\ReverseProxyGuard
|
||||
* @static
|
||||
*/ public static function setUser($user)
|
||||
{
|
||||
/** @var \App\Services\Auth\ReverseProxyGuard $instance */
|
||||
return $instance->setUser($user);
|
||||
}
|
||||
/**
|
||||
* Forget the current user.
|
||||
*
|
||||
* @return \App\Services\Auth\ReverseProxyGuard
|
||||
* @return \Illuminate\Auth\SessionGuard
|
||||
* @static
|
||||
*/ public static function forgetUser()
|
||||
{
|
||||
/** @var \App\Services\Auth\ReverseProxyGuard $instance */
|
||||
/** @var \Illuminate\Auth\SessionGuard $instance */
|
||||
return $instance->forgetUser();
|
||||
}
|
||||
/**
|
||||
@ -1949,7 +2210,7 @@
|
||||
* @static
|
||||
*/ public static function getProvider()
|
||||
{
|
||||
/** @var \App\Services\Auth\ReverseProxyGuard $instance */
|
||||
/** @var \Illuminate\Auth\SessionGuard $instance */
|
||||
return $instance->getProvider();
|
||||
}
|
||||
/**
|
||||
@ -1960,8 +2221,50 @@
|
||||
* @static
|
||||
*/ public static function setProvider($provider)
|
||||
{
|
||||
/** @var \App\Services\Auth\ReverseProxyGuard $instance */
|
||||
/** @var \Illuminate\Auth\SessionGuard $instance */
|
||||
$instance->setProvider($provider);
|
||||
}
|
||||
/**
|
||||
* Register a custom macro.
|
||||
*
|
||||
* @param string $name
|
||||
* @param object|callable $macro
|
||||
* @return void
|
||||
* @static
|
||||
*/ public static function macro($name, $macro)
|
||||
{
|
||||
\Illuminate\Auth\SessionGuard::macro($name, $macro);
|
||||
}
|
||||
/**
|
||||
* Mix another object into the class.
|
||||
*
|
||||
* @param object $mixin
|
||||
* @param bool $replace
|
||||
* @return void
|
||||
* @throws \ReflectionException
|
||||
* @static
|
||||
*/ public static function mixin($mixin, $replace = true)
|
||||
{
|
||||
\Illuminate\Auth\SessionGuard::mixin($mixin, $replace);
|
||||
}
|
||||
/**
|
||||
* Checks if macro is registered.
|
||||
*
|
||||
* @param string $name
|
||||
* @return bool
|
||||
* @static
|
||||
*/ public static function hasMacro($name)
|
||||
{
|
||||
return \Illuminate\Auth\SessionGuard::hasMacro($name);
|
||||
}
|
||||
/**
|
||||
* Flush the existing macros.
|
||||
*
|
||||
* @return void
|
||||
* @static
|
||||
*/ public static function flushMacros()
|
||||
{
|
||||
\Illuminate\Auth\SessionGuard::flushMacros();
|
||||
}
|
||||
}
|
||||
/**
|
||||
@ -8569,6 +8872,17 @@
|
||||
{
|
||||
/** @var \Illuminate\Support\Testing\Fakes\NotificationFake $instance */
|
||||
return $instance->hasSent($notifiable, $notification);
|
||||
}
|
||||
/**
|
||||
* Specify if notification should be serialized and restored when being "pushed" to the queue.
|
||||
*
|
||||
* @param bool $serializeAndRestore
|
||||
* @return \Illuminate\Support\Testing\Fakes\NotificationFake
|
||||
* @static
|
||||
*/ public static function serializeAndRestore($serializeAndRestore = true)
|
||||
{
|
||||
/** @var \Illuminate\Support\Testing\Fakes\NotificationFake $instance */
|
||||
return $instance->serializeAndRestore($serializeAndRestore);
|
||||
}
|
||||
/**
|
||||
* Get the notifications that have been sent.
|
||||
|
@ -24,17 +24,17 @@
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\AuthenticationLog;
|
||||
use App\Models\AuthLog;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class PurgeAuthenticationLog extends Command
|
||||
class PurgeAuthLog extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $signature = 'authentication-log:purge';
|
||||
public $signature = '2fauth:purge-log';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
@ -50,7 +50,7 @@ public function handle() : void
|
||||
{
|
||||
$this->comment('Clearing authentication log...');
|
||||
|
||||
$deleted = AuthenticationLog::where('login_at', '<', now()->subDays(config('authentication-log.purge'))->format('Y-m-d H:i:s'))->delete();
|
||||
$deleted = AuthLog::where('login_at', '<', now()->subDays(config('2fauth.authLogRetentionTime'))->format('Y-m-d H:i:s'))->delete();
|
||||
|
||||
$this->info($deleted . ' authentication logs cleared.');
|
||||
}
|
@ -70,7 +70,7 @@ protected function flushDB() : void
|
||||
DB::table('groups')->delete();
|
||||
DB::table('users')->delete();
|
||||
DB::table('options')->delete();
|
||||
DB::table(config('authentication-log.table_name'))->delete();
|
||||
DB::table('auth_logs')->delete();
|
||||
|
||||
$this->line('Database cleaned');
|
||||
}
|
||||
|
26
app/Events/VisitedByProxyUser.php
Normal file
26
app/Events/VisitedByProxyUser.php
Normal file
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace App\Events;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Contracts\Auth\Authenticatable;
|
||||
|
||||
class VisitedByProxyUser
|
||||
{
|
||||
/**
|
||||
* The authenticated user.
|
||||
*
|
||||
* @var User|Authenticatable
|
||||
*/
|
||||
public $user;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(User|Authenticatable $user)
|
||||
{
|
||||
$this->user = $user;
|
||||
}
|
||||
}
|
@ -4,7 +4,7 @@
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
abstract class AccessAbstractListener
|
||||
abstract class AbstractAccessListener
|
||||
{
|
||||
/**
|
||||
* The current request
|
||||
@ -22,6 +22,8 @@ public function __construct(Request $request)
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the event.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
abstract public function handle(mixed $event);
|
@ -1,81 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* The MIT License (MIT)
|
||||
* Copyright (c) 2024 Bubka
|
||||
* Copyright (c) 2024 Anthony Rappa
|
||||
* Copyright (c) 2017 Yaakov Dahan
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
|
||||
* associated documentation files (the "Software"), to deal in the Software without restriction,
|
||||
* including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
* and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all copies or substantial
|
||||
* portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
|
||||
* LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
namespace App\Listeners\Authentication;
|
||||
|
||||
use Illuminate\Auth\Events\Login;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Carbon;
|
||||
use App\Notifications\SignedInWithNewDevice;
|
||||
use App\Models\Traits\AuthenticationLoggable;
|
||||
|
||||
class AuthProxyListener extends AccessAbstractListener
|
||||
{
|
||||
public Request $request;
|
||||
|
||||
public function __construct(Request $request)
|
||||
{
|
||||
$this->request = $request;
|
||||
}
|
||||
|
||||
public function handle(mixed $event): void
|
||||
{
|
||||
$listener = config('authentication-log.events.login', Login::class);
|
||||
|
||||
if (! $event instanceof $listener) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($event->user) {
|
||||
if(! in_array(AuthenticationLoggable::class, class_uses_recursive(get_class($event->user)))) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (config('authentication-log.behind_cdn')) {
|
||||
$ip = $this->request->server(config('authentication-log.behind_cdn.http_header_field'));
|
||||
} else {
|
||||
$ip = $this->request->ip();
|
||||
}
|
||||
|
||||
$user = $event->user;
|
||||
$userAgent = $this->request->userAgent();
|
||||
$known = $user->authentications()->whereIpAddress($ip)->whereUserAgent($userAgent)->whereLoginSuccessful(true)->first();
|
||||
$newUser = Carbon::parse($user->{$user->getCreatedAtColumn()})->diffInMinutes(Carbon::now()) < 1;
|
||||
|
||||
/** @disregard Undefined function */
|
||||
$log = $user->authentications()->create([
|
||||
'ip_address' => $ip,
|
||||
'user_agent' => $userAgent,
|
||||
'login_at' => now(),
|
||||
'login_successful' => true,
|
||||
'location' => config('authentication-log.notifications.new-device.location') ? optional(geoip()->getLocation($ip))->toArray() : null
|
||||
]);
|
||||
|
||||
if (! $known && ! $newUser && config('authentication-log.notifications.new-device.enabled')) {
|
||||
$newDevice = config('authentication-log.notifications.new-device.template') ?? SignedInWithNewDevice::class;
|
||||
$user->notify(new $newDevice($log));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -25,54 +25,40 @@
|
||||
namespace App\Listeners\Authentication;
|
||||
|
||||
use App\Notifications\FailedLogin;
|
||||
use App\Models\Traits\AuthenticationLoggable;
|
||||
use Illuminate\Auth\Events\Failed;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class FailedLoginListener extends AccessAbstractListener
|
||||
class FailedLoginListener extends AbstractAccessListener
|
||||
{
|
||||
public Request $request;
|
||||
|
||||
public function __construct(Request $request)
|
||||
{
|
||||
$this->request = $request;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the event.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle(mixed $event) : void
|
||||
{
|
||||
$listener = config('authentication-log.events.failed', Failed::class);
|
||||
|
||||
if (! $event instanceof $listener) {
|
||||
if (! $event instanceof Failed) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($event->user) {
|
||||
if (! in_array(AuthenticationLoggable::class, class_uses_recursive(get_class($event->user)))) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (config('authentication-log.behind_cdn')) {
|
||||
$ip = $this->request->server(config('authentication-log.behind_cdn.http_header_field'));
|
||||
} else {
|
||||
$ip = $this->request->ip();
|
||||
}
|
||||
|
||||
/**
|
||||
* @var \App\Models\User
|
||||
*/
|
||||
$user = $event->user;
|
||||
$guard = $event->guard;
|
||||
$ip = config('2fauth.proxy_headers.forIp') ?? $this->request->ip();
|
||||
|
||||
/** @disregard Undefined function */
|
||||
$log = $event->user->authentications()->create([
|
||||
$log = $user->authentications()->create([
|
||||
'ip_address' => $ip,
|
||||
'user_agent' => $this->request->userAgent(),
|
||||
'login_at' => now(),
|
||||
'login_successful' => false,
|
||||
'location' => config('authentication-log.notifications.new-device.location') ? optional(geoip()->getLocation($ip))->toArray() : null,
|
||||
'guard' => $guard,
|
||||
'login_method' => $this->loginMethod(),
|
||||
]);
|
||||
|
||||
if (config('authentication-log.notifications.failed-login.enabled')) {
|
||||
$failedLogin = config('authentication-log.notifications.failed-login.template') ?? FailedLogin::class;
|
||||
$event->user->notify(new $failedLogin($log));
|
||||
if ($user->preferences['notifyOnFailedLogin']) {
|
||||
$user->notify(new FailedLogin($log));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -24,57 +24,44 @@
|
||||
|
||||
namespace App\Listeners\Authentication;
|
||||
|
||||
use App\Models\Traits\AuthenticationLoggable;
|
||||
use App\Notifications\SignedInWithNewDevice;
|
||||
use Illuminate\Auth\Events\Login;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Carbon;
|
||||
|
||||
class LoginListener extends AccessAbstractListener
|
||||
class LoginListener extends AbstractAccessListener
|
||||
{
|
||||
/**
|
||||
*
|
||||
* Handle the event.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle(mixed $event) : void
|
||||
{
|
||||
$listener = config('authentication-log.events.login', Login::class);
|
||||
|
||||
if (! $event instanceof $listener) {
|
||||
if (! $event instanceof Login) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($event->user) {
|
||||
if (! in_array(AuthenticationLoggable::class, class_uses_recursive(get_class($event->user)))) {
|
||||
return;
|
||||
}
|
||||
/**
|
||||
* @var \App\Models\User
|
||||
*/
|
||||
$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()) < 1;
|
||||
$guard = $event->guard;
|
||||
|
||||
if (config('authentication-log.behind_cdn')) {
|
||||
$ip = $this->request->server(config('authentication-log.behind_cdn.http_header_field'));
|
||||
} else {
|
||||
$ip = $this->request->ip();
|
||||
}
|
||||
$log = $user->authentications()->create([
|
||||
'ip_address' => $ip,
|
||||
'user_agent' => $userAgent,
|
||||
'login_at' => now(),
|
||||
'login_successful' => true,
|
||||
'guard' => $guard,
|
||||
'login_method' => $this->loginMethod(),
|
||||
]);
|
||||
|
||||
$user = $event->user;
|
||||
$userAgent = $this->request->userAgent();
|
||||
$known = $user->authentications()->whereIpAddress($ip)->whereUserAgent($userAgent)->whereLoginSuccessful(true)->first();
|
||||
$newUser = Carbon::parse($user->{$user->getCreatedAtColumn()})->diffInMinutes(Carbon::now()) < 1;
|
||||
$guard = $event->guard;
|
||||
|
||||
/** @disregard Undefined function */
|
||||
$log = $user->authentications()->create([
|
||||
'ip_address' => $ip,
|
||||
'user_agent' => $userAgent,
|
||||
'login_at' => now(),
|
||||
'login_successful' => true,
|
||||
'location' => config('authentication-log.notifications.new-device.location') ? optional(geoip()->getLocation($ip))->toArray() : null,
|
||||
'guard' => $guard,
|
||||
'login_method' => $this->loginMethod(),
|
||||
]);
|
||||
|
||||
if (! $known && ! $newUser && config('authentication-log.notifications.new-device.enabled')) {
|
||||
$newDevice = config('authentication-log.notifications.new-device.template') ?? SignedInWithNewDevice::class;
|
||||
$user->notify(new $newDevice($log));
|
||||
}
|
||||
if (! $known && ! $newUser && $user->preferences['notifyOnNewAuthDevice']) {
|
||||
$user->notify(new SignedInWithNewDevice($log));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -24,56 +24,40 @@
|
||||
|
||||
namespace App\Listeners\Authentication;
|
||||
|
||||
use App\Models\AuthenticationLog;
|
||||
use App\Models\Traits\AuthenticationLoggable;
|
||||
use App\Models\AuthLog;
|
||||
use Illuminate\Auth\Events\Logout;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class LogoutListener extends AccessAbstractListener
|
||||
class LogoutListener extends AbstractAccessListener
|
||||
{
|
||||
public Request $request;
|
||||
|
||||
public function __construct(Request $request)
|
||||
{
|
||||
$this->request = $request;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the event.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle(mixed $event) : void
|
||||
{
|
||||
$listener = config('authentication-log.events.logout', Logout::class);
|
||||
|
||||
if (! $event instanceof $listener) {
|
||||
if (! $event instanceof Logout) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($event->user) {
|
||||
if (! in_array(AuthenticationLoggable::class, class_uses_recursive(get_class($event->user)))) {
|
||||
return;
|
||||
}
|
||||
/**
|
||||
* @var \App\Models\User
|
||||
*/
|
||||
$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;
|
||||
|
||||
$user = $event->user;
|
||||
|
||||
if (config('authentication-log.behind_cdn')) {
|
||||
$ip = $this->request->server(config('authentication-log.behind_cdn.http_header_field'));
|
||||
} else {
|
||||
$ip = $this->request->ip();
|
||||
}
|
||||
|
||||
$userAgent = $this->request->userAgent();
|
||||
$log = $user->authentications()->whereIpAddress($ip)->whereUserAgent($userAgent)->whereGuard($event->guard)->orderByDesc('login_at')->first();
|
||||
$guard = $event->guard;
|
||||
|
||||
if (! $log) {
|
||||
$log = new AuthenticationLog([
|
||||
'ip_address' => $ip,
|
||||
'user_agent' => $userAgent,
|
||||
'guard' => $guard,
|
||||
]);
|
||||
}
|
||||
|
||||
$log->logout_at = now();
|
||||
|
||||
$user->authentications()->save($log);
|
||||
if (! $log) {
|
||||
$log = new AuthLog([
|
||||
'ip_address' => $ip,
|
||||
'user_agent' => $userAgent,
|
||||
'guard' => $guard,
|
||||
]);
|
||||
}
|
||||
|
||||
$log->logout_at = now();
|
||||
$user->authentications()->save($log);
|
||||
}
|
||||
}
|
||||
|
@ -24,61 +24,46 @@
|
||||
|
||||
namespace App\Listeners\Authentication;
|
||||
|
||||
use App\Models\AuthenticationLog;
|
||||
use App\Models\Traits\AuthenticationLoggable;
|
||||
use App\Models\AuthLog;
|
||||
use Illuminate\Auth\Events\OtherDeviceLogout;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class OtherDeviceLogoutListener extends AccessAbstractListener
|
||||
class OtherDeviceLogoutListener extends AbstractAccessListener
|
||||
{
|
||||
public Request $request;
|
||||
|
||||
public function __construct(Request $request)
|
||||
{
|
||||
$this->request = $request;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the event.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle(mixed $event) : void
|
||||
{
|
||||
$listener = config('authentication-log.events.other-device-logout', OtherDeviceLogout::class);
|
||||
|
||||
if (! $event instanceof $listener) {
|
||||
if (! $event instanceof OtherDeviceLogout) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($event->user) {
|
||||
if (! in_array(AuthenticationLoggable::class, class_uses_recursive(get_class($event->user)))) {
|
||||
return;
|
||||
}
|
||||
/**
|
||||
* @var \App\Models\User
|
||||
*/
|
||||
$user = $event->user;
|
||||
$ip = config('2fauth.proxy_headers.forIp') ?? $this->request->ip();
|
||||
$userAgent = $this->request->userAgent();
|
||||
$authLog = $user->authentications()->whereIpAddress($ip)->whereUserAgent($userAgent)->first();
|
||||
$guard = $event->guard;
|
||||
|
||||
$user = $event->user;
|
||||
if (! $authLog) {
|
||||
$authLog = new AuthLog([
|
||||
'ip_address' => $ip,
|
||||
'user_agent' => $userAgent,
|
||||
'guard' => $guard,
|
||||
]);
|
||||
}
|
||||
|
||||
if (config('authentication-log.behind_cdn')) {
|
||||
$ip = $this->request->server(config('authentication-log.behind_cdn.http_header_field'));
|
||||
} else {
|
||||
$ip = $this->request->ip();
|
||||
}
|
||||
|
||||
$userAgent = $this->request->userAgent();
|
||||
$authenticationLog = $user->authentications()->whereIpAddress($ip)->whereUserAgent($userAgent)->first();
|
||||
$guard = $event->guard;
|
||||
|
||||
if (! $authenticationLog) {
|
||||
$authenticationLog = new AuthenticationLog([
|
||||
'ip_address' => $ip,
|
||||
'user_agent' => $userAgent,
|
||||
'guard' => $guard,
|
||||
foreach ($user->authentications()->whereLoginSuccessful(true)->whereNull('logout_at')->get() as $log) {
|
||||
if ($log->id !== $authLog->id) {
|
||||
$log->update([
|
||||
'cleared_by_user' => true,
|
||||
'logout_at' => now(),
|
||||
]);
|
||||
}
|
||||
|
||||
foreach ($user->authentications()->whereLoginSuccessful(true)->whereNull('logout_at')->get() as $log) {
|
||||
if ($log->id !== $authenticationLog->id) {
|
||||
$log->update([
|
||||
'cleared_by_user' => true,
|
||||
'logout_at' => now(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,56 +3,41 @@
|
||||
namespace App\Authentication\Listeners;
|
||||
|
||||
use App\Events\VisitedByProxyUser;
|
||||
use App\Listeners\Authentication\AccessAbstractListener;
|
||||
use App\Models\Traits\AuthenticationLoggable;
|
||||
use App\Listeners\Authentication\AbstractAccessListener;
|
||||
use App\Notifications\SignedInWithNewDevice;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Carbon;
|
||||
|
||||
class VisitedByProxyUserListener extends AccessAbstractListener
|
||||
class VisitedByProxyUserListener extends AbstractAccessListener
|
||||
{
|
||||
public Request $request;
|
||||
|
||||
public function __construct(Request $request)
|
||||
{
|
||||
$this->request = $request;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the event.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle(mixed $event): void
|
||||
{
|
||||
if (! $event instanceof VisitedByProxyUser) {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* @var \App\Models\User
|
||||
*/
|
||||
$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()) < 1;
|
||||
|
||||
if ($event->user) {
|
||||
if(! in_array(AuthenticationLoggable::class, class_uses_recursive(get_class($event->user)))) {
|
||||
return;
|
||||
}
|
||||
$log = $user->authentications()->create([
|
||||
'ip_address' => $ip,
|
||||
'user_agent' => $userAgent,
|
||||
'login_at' => now(),
|
||||
'login_successful' => true,
|
||||
]);
|
||||
|
||||
if (config('authentication-log.behind_cdn')) {
|
||||
$ip = $this->request->server(config('authentication-log.behind_cdn.http_header_field'));
|
||||
} else {
|
||||
$ip = $this->request->ip();
|
||||
}
|
||||
|
||||
$user = $event->user;
|
||||
$userAgent = $this->request->userAgent();
|
||||
$known = $user->authentications()->whereIpAddress($ip)->whereUserAgent($userAgent)->whereLoginSuccessful(true)->first();
|
||||
$newUser = Carbon::parse($user->{$user->getCreatedAtColumn()})->diffInMinutes(Carbon::now()) < 1;
|
||||
|
||||
/** @disregard Undefined function */
|
||||
$log = $user->authentications()->create([
|
||||
'ip_address' => $ip,
|
||||
'user_agent' => $userAgent,
|
||||
'login_at' => now(),
|
||||
'login_successful' => true,
|
||||
'location' => config('authentication-log.notifications.new-device.location') ? optional(geoip()->getLocation($ip))->toArray() : null,
|
||||
]);
|
||||
|
||||
if (! $known && ! $newUser && config('authentication-log.notifications.new-device.enabled')) {
|
||||
$newDevice = config('authentication-log.notifications.new-device.template') ?? SignedInWithNewDevice::class;
|
||||
$user->notify(new $newDevice($log));
|
||||
}
|
||||
if (! $known && ! $newUser && $user->preferences['notifyOnNewAuthDevice']) {
|
||||
$user->notify(new SignedInWithNewDevice($log));
|
||||
}
|
||||
}
|
||||
}
|
@ -37,22 +37,16 @@
|
||||
* @property bool $login_successful
|
||||
* @property \Illuminate\Support\Carbon|null $logout_at
|
||||
* @property bool $cleared_by_user
|
||||
* @property array|null $location
|
||||
* @property string|null $guard
|
||||
* @property string|null $method
|
||||
*/
|
||||
class AuthenticationLog extends Model
|
||||
class AuthLog extends Model
|
||||
{
|
||||
/**
|
||||
* Indicates if the model should be timestamped.
|
||||
*/
|
||||
public $timestamps = false;
|
||||
|
||||
/**
|
||||
* The table associated with the model.
|
||||
*/
|
||||
protected $table = 'authentication_log';
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*/
|
||||
@ -63,7 +57,6 @@ class AuthenticationLog extends Model
|
||||
'login_successful',
|
||||
'logout_at',
|
||||
'cleared_by_user',
|
||||
'location',
|
||||
'guard',
|
||||
'login_method',
|
||||
];
|
||||
@ -73,36 +66,15 @@ class AuthenticationLog extends Model
|
||||
*/
|
||||
protected $casts = [
|
||||
'cleared_by_user' => 'boolean',
|
||||
'location' => 'array',
|
||||
'login_successful' => 'boolean',
|
||||
'login_at' => 'datetime',
|
||||
'logout_at' => 'datetime',
|
||||
];
|
||||
|
||||
/**
|
||||
* Create a new Eloquent AuthenticationLog instance
|
||||
*/
|
||||
public function __construct(array $attributes = [])
|
||||
{
|
||||
if (! isset($this->connection)) {
|
||||
$this->setConnection(config('authentication-log.db_connection'));
|
||||
}
|
||||
|
||||
parent::__construct($attributes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the table associated with the model.
|
||||
*/
|
||||
public function getTable()
|
||||
{
|
||||
return config('authentication-log.table_name', parent::getTable());
|
||||
}
|
||||
|
||||
/**
|
||||
* MorphTo relation to get the associated authenticatable user
|
||||
*
|
||||
* @return MorphTo<\Illuminate\Database\Eloquent\Model, AuthenticationLog>
|
||||
* @return MorphTo<\Illuminate\Database\Eloquent\Model, AuthLog>
|
||||
*/
|
||||
public function authenticatable()
|
||||
{
|
@ -24,32 +24,31 @@
|
||||
|
||||
namespace App\Models\Traits;
|
||||
|
||||
use App\Models\AuthenticationLog;
|
||||
use App\Models\AuthLog;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
trait AuthenticationLoggable
|
||||
trait HasAuthenticationLog
|
||||
{
|
||||
/**
|
||||
* Get all user's authentications from the auth log
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Relations\MorphMany<AuthenticationLog>
|
||||
* @return \Illuminate\Database\Eloquent\Relations\MorphMany<AuthLog>
|
||||
*/
|
||||
public function authentications()
|
||||
{
|
||||
return $this->morphMany(AuthenticationLog::class, 'authenticatable')->latest('id');
|
||||
return $this->morphMany(AuthLog::class, 'authenticatable')->latest('id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get authentications for the provided timespan (in month)
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Collection<int, AuthenticationLog>
|
||||
* @return \Illuminate\Database\Eloquent\Collection<int, AuthLog>
|
||||
*/
|
||||
public function authenticationsByPeriod(int $period = 1)
|
||||
{
|
||||
$from = Carbon::now()->subMonths($period);
|
||||
|
||||
return $this->authentications->filter(function (AuthenticationLog $authentication) use ($from) {
|
||||
return $this->authentications->filter(function (AuthLog $authentication) use ($from) {
|
||||
return $authentication->login_at >= $from || $authentication->logout_at >= $from;
|
||||
});
|
||||
}
|
||||
@ -57,11 +56,11 @@ public function authenticationsByPeriod(int $period = 1)
|
||||
/**
|
||||
* Get the user's latest authentication
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Relations\MorphOne<AuthenticationLog>
|
||||
* @return \Illuminate\Database\Eloquent\Relations\MorphOne<AuthLog>
|
||||
*/
|
||||
public function latestAuthentication()
|
||||
{
|
||||
return $this->morphOne(AuthenticationLog::class, 'authenticatable')->latestOfMany('login_at');
|
||||
return $this->morphOne(AuthLog::class, 'authenticatable')->latestOfMany('login_at');
|
||||
}
|
||||
|
||||
/**
|
||||
@ -115,7 +114,7 @@ public function previousLoginIp() : ?string
|
||||
/**
|
||||
* The notification channels to be used for notifications
|
||||
*/
|
||||
public function notifyAuthenticationLogVia() : array
|
||||
public function notifyAuthLogVia() : array
|
||||
{
|
||||
return ['mail'];
|
||||
}
|
@ -3,7 +3,7 @@
|
||||
namespace App\Models;
|
||||
|
||||
use App\Models\Traits\WebAuthnManageCredentials;
|
||||
use App\Models\Traits\AuthenticationLoggable;
|
||||
use App\Models\Traits\HasAuthenticationLog;
|
||||
use Illuminate\Auth\Events\PasswordReset;
|
||||
use Illuminate\Auth\Notifications\ResetPassword;
|
||||
use Illuminate\Contracts\Translation\HasLocalePreference;
|
||||
@ -43,9 +43,9 @@
|
||||
* @property-read int|null $web_authn_credentials_count
|
||||
* @property string|null $oauth_id
|
||||
* @property string|null $oauth_provider
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\AuthenticationLog> $authentications
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\AuthLog> $authentications
|
||||
* @property-read int|null $authentications_count
|
||||
* @property-read \App\Models\AuthenticationLog|null $latestAuthentication
|
||||
* @property-read \App\Models\AuthLog|null $latestAuthentication
|
||||
*
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|User admins()
|
||||
*
|
||||
@ -53,7 +53,7 @@
|
||||
*/
|
||||
class User extends Authenticatable implements HasLocalePreference, WebAuthnAuthenticatable
|
||||
{
|
||||
use AuthenticationLoggable;
|
||||
use HasAuthenticationLog;
|
||||
use HasApiTokens, HasFactory, Notifiable;
|
||||
use WebAuthnAuthentication, WebAuthnManageCredentials;
|
||||
|
||||
|
@ -2,24 +2,37 @@
|
||||
|
||||
namespace App\Notifications;
|
||||
|
||||
use App\Models\AuthenticationLog;
|
||||
use App\Models\AuthLog;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
use Illuminate\Notifications\Notification;
|
||||
use Jenssegers\Agent\Agent;
|
||||
|
||||
class FailedLogin extends Notification implements ShouldQueue
|
||||
{
|
||||
use Queueable;
|
||||
|
||||
/**
|
||||
* The AuthenticationLog model instance
|
||||
* A user agent parser instance.
|
||||
*
|
||||
* @var mixed
|
||||
*/
|
||||
public AuthenticationLog $authenticationLog;
|
||||
protected $agent;
|
||||
|
||||
public function __construct(AuthenticationLog $authenticationLog)
|
||||
/**
|
||||
* The AuthLog model instance
|
||||
*/
|
||||
public AuthLog $authLog;
|
||||
|
||||
/**
|
||||
* Create a new FailedLogin instance
|
||||
*/
|
||||
public function __construct(AuthLog $authLog)
|
||||
{
|
||||
$this->authenticationLog = $authenticationLog;
|
||||
$this->authLog = $authLog;
|
||||
$this->agent = new Agent();
|
||||
$this->agent->setUserAgent($authLog->user_agent);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -27,7 +40,7 @@ public function __construct(AuthenticationLog $authenticationLog)
|
||||
*/
|
||||
public function via(mixed $notifiable) : array|string
|
||||
{
|
||||
return $notifiable->notifyAuthenticationLogVia();
|
||||
return $notifiable->notifyAuthLogVia();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -36,13 +49,13 @@ public function via(mixed $notifiable) : array|string
|
||||
public function toMail(mixed $notifiable) : MailMessage
|
||||
{
|
||||
return (new MailMessage())
|
||||
->subject(__('A failed login to your account'))
|
||||
->markdown('authentication-log::emails.failed', [
|
||||
->subject(__('notifications.failed_login.subject'))
|
||||
->markdown('emails.failedLogin', [
|
||||
'account' => $notifiable,
|
||||
'time' => $this->authenticationLog->login_at,
|
||||
'ipAddress' => $this->authenticationLog->ip_address,
|
||||
'browser' => $this->authenticationLog->user_agent,
|
||||
'location' => $this->authenticationLog->location,
|
||||
'time' => $this->authLog->login_at,
|
||||
'ipAddress' => $this->authLog->ip_address,
|
||||
'browser' => $this->authLog->user_agent,
|
||||
'platform' => $this->agent->platform(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
namespace App\Notifications;
|
||||
|
||||
use App\Models\AuthenticationLog;
|
||||
use App\Models\AuthLog;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
@ -12,8 +12,11 @@
|
||||
class SignedInWithNewDevice extends Notification implements ShouldQueue
|
||||
{
|
||||
use Queueable;
|
||||
|
||||
public AuthenticationLog $authenticationLog;
|
||||
|
||||
/**
|
||||
* The AuthLog model instance
|
||||
*/
|
||||
public AuthLog $authLog;
|
||||
|
||||
/**
|
||||
* A user agent parser instance.
|
||||
@ -25,16 +28,16 @@ class SignedInWithNewDevice extends Notification implements ShouldQueue
|
||||
/**
|
||||
* Create a new SignedInWithNewDevice instance
|
||||
*/
|
||||
public function __construct(AuthenticationLog $authenticationLog)
|
||||
public function __construct(AuthLog $authLog)
|
||||
{
|
||||
$this->authenticationLog = $authenticationLog;
|
||||
$this->agent = new Agent();
|
||||
$this->agent->setUserAgent($authenticationLog->user_agent);
|
||||
$this->authLog = $authLog;
|
||||
$this->agent = new Agent();
|
||||
$this->agent->setUserAgent($authLog->user_agent);
|
||||
}
|
||||
|
||||
public function via(mixed $notifiable) : array|string
|
||||
{
|
||||
return $notifiable->notifyAuthenticationLogVia();
|
||||
return $notifiable->notifyAuthLogVia();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -44,10 +47,10 @@ public function toMail(mixed $notifiable) : MailMessage
|
||||
{
|
||||
return (new MailMessage())
|
||||
->subject(__('notifications.new_device.subject'))
|
||||
->markdown('emails.newDevice', [
|
||||
->markdown('emails.SignedInWithNewDevice', [
|
||||
'account' => $notifiable,
|
||||
'time' => $this->authenticationLog->login_at,
|
||||
'ipAddress' => $this->authenticationLog->ip_address,
|
||||
'time' => $this->authLog->login_at,
|
||||
'ipAddress' => $this->authLog->ip_address,
|
||||
'browser' => $this->agent->browser(),
|
||||
'platform' => $this->agent->platform(),
|
||||
]);
|
||||
|
@ -4,6 +4,7 @@
|
||||
|
||||
use App\Events\GroupDeleted;
|
||||
use App\Events\GroupDeleting;
|
||||
use App\Events\VisitedByProxyUser;
|
||||
use App\Events\ScanForNewReleaseCalled;
|
||||
use App\Events\TwoFAccountDeleted;
|
||||
use App\Listeners\Authentication\FailedLoginListener;
|
||||
@ -64,6 +65,9 @@ class EventServiceProvider extends ServiceProvider
|
||||
Logout::class => [
|
||||
LogoutListener::class,
|
||||
],
|
||||
// VisitedByProxyUser::class => [
|
||||
// ProxyUserAccessListener::class,
|
||||
// ],
|
||||
];
|
||||
|
||||
/**
|
||||
|
@ -28,6 +28,18 @@
|
||||
'outgoingProxy' => env('PROXY_FOR_OUTGOING_REQUESTS', ''),
|
||||
'proxyLogoutUrl' => env('PROXY_LOGOUT_URL', null),
|
||||
'appSubdirectory' => env('APP_SUBDIRECTORY', ''),
|
||||
'authLogRetentionTime' => env('AUTHENTICATION_LOG_RETENTION', 365),
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Proxy headers
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
*/
|
||||
|
||||
'proxy_headers' => [
|
||||
'forIp' => env('PROXY_HEADER_FOR_IP', null),
|
||||
],
|
||||
|
||||
/*
|
||||
@ -110,6 +122,8 @@
|
||||
'formatPasswordBy' => 0.5,
|
||||
'lang' => 'browser',
|
||||
'getOtpOnRequest' => true,
|
||||
'notifyOnNewAuthDevice' => true,
|
||||
'notifyOnFailedLogin' => true,
|
||||
],
|
||||
|
||||
];
|
||||
|
@ -1,71 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* The MIT License (MIT)
|
||||
* Copyright (c) 2024 Bubka
|
||||
* Copyright (c) 2024 Anthony Rappa
|
||||
* Copyright (c) 2017 Yaakov Dahan
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
|
||||
* associated documentation files (the "Software"), to deal in the Software without restriction,
|
||||
* including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
* and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all copies or substantial
|
||||
* portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
|
||||
* LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
use App\Notifications\FailedLogin;
|
||||
use App\Notifications\SignedInWithNewDevice;
|
||||
|
||||
return [
|
||||
// The database table name
|
||||
// You can change this if the database keys get too long for your driver
|
||||
'table_name' => 'authentication_log',
|
||||
|
||||
// The database connection where the authentication_log table resides. Leave empty to use the default
|
||||
'db_connection' => null,
|
||||
|
||||
'notifications' => [
|
||||
'new-device' => [
|
||||
// Send the NewDevice notification
|
||||
'enabled' => env('NEW_DEVICE_NOTIFICATION', true),
|
||||
|
||||
// Use torann/geoip to attempt to get a location
|
||||
'location' => false,
|
||||
|
||||
// The Notification class to send
|
||||
'template' => SignedInWithNewDevice::class,
|
||||
],
|
||||
'failed-login' => [
|
||||
// Send the FailedLogin notification
|
||||
'enabled' => env('FAILED_LOGIN_NOTIFICATION', false),
|
||||
|
||||
// Use torann/geoip to attempt to get a location
|
||||
'location' => false,
|
||||
|
||||
// The Notification class to send
|
||||
'template' => FailedLogin::class,
|
||||
],
|
||||
],
|
||||
|
||||
// When the clean-up command is run, delete old logs greater than `purge` days
|
||||
// Don't schedule the clean-up command if you want to keep logs forever.
|
||||
'purge' => 365,
|
||||
|
||||
// If you are behind an CDN proxy, set 'behind_cdn.http_header_field' to the corresponding http header field of your cdn
|
||||
// For cloudflare you can have look at: https://developers.cloudflare.com/fundamentals/get-started/reference/http-request-headers/
|
||||
// 'behind_cdn' => [
|
||||
// 'http_header_field' => 'HTTP_CF_CONNECTING_IP' // used by Cloudflare
|
||||
// ],
|
||||
|
||||
// If you are not a cdn user, use false
|
||||
'behind_cdn' => false,
|
||||
];
|
@ -30,7 +30,7 @@
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create(config('authentication-log.table_name'), function (Blueprint $table) {
|
||||
Schema::create('auth_logs', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->morphs('authenticatable');
|
||||
$table->string('ip_address', 45)->nullable();
|
||||
@ -39,7 +39,6 @@ public function up(): void
|
||||
$table->boolean('login_successful')->default(false);
|
||||
$table->timestamp('logout_at')->nullable();
|
||||
$table->boolean('cleared_by_user')->default(false);
|
||||
$table->json('location')->nullable();
|
||||
$table->string('guard', 40)->nullable();
|
||||
$table->string('login_method', 40)->nullable();
|
||||
});
|
||||
@ -47,6 +46,6 @@ public function up(): void
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists(config('authentication-log.table_name'));
|
||||
Schema::dropIfExists('auth_logs');
|
||||
}
|
||||
};
|
@ -83,6 +83,9 @@ services:
|
||||
# authentication checks. That means your proxy is fully responsible of the authentication process, 2FAuth will
|
||||
# trust him as long as headers are presents.
|
||||
- AUTHENTICATION_GUARD=web-guard
|
||||
# Authentication log retention time, in days.
|
||||
# Log entries older than that are automatically deleted.
|
||||
- AUTHENTICATION_LOG_RETENTION=365
|
||||
# Name of the HTTP headers sent by the reverse proxy that identifies the authenticated user at proxy level.
|
||||
# Check your proxy documentation to find out how these headers are named (i.e 'REMOTE_USER', 'REMOTE_EMAIL', etc...)
|
||||
# (only relevant when AUTHENTICATION_GUARD is set to 'reverse-proxy-guard')
|
||||
|
@ -11,6 +11,4 @@ parameters:
|
||||
analyse:
|
||||
- app/Protobuf/*
|
||||
ignoreErrors:
|
||||
-
|
||||
message: '#.*geoip.*#'
|
||||
checkMissingIterableValueType: false
|
@ -27,4 +27,10 @@
|
||||
'connection_details' => 'Here are the details of this connection',
|
||||
'recommandations' => 'If this was you, you can ignore this alert. If you suspect any suspicious activity on your account, please change your password.'
|
||||
],
|
||||
'failed_login' => [
|
||||
'subject' => 'Failed login to 2FAuth',
|
||||
'resume' => 'There has been a failed login attempt to your 2FAuth account.',
|
||||
'connection_details' => 'Here are the details of this connection attempt',
|
||||
'recommandations' => 'If this was you, you can ignore this alert. If further attempts fail, you should contact the 2FAuth administrator to review security settings and take action against this attacker.'
|
||||
],
|
||||
];
|
18
resources/views/emails/failed.blade.php
vendored
18
resources/views/emails/failed.blade.php
vendored
@ -1,18 +0,0 @@
|
||||
@component('mail::message')
|
||||
# @lang('Hello!')
|
||||
|
||||
@lang('There has been a failed login attempt to your :app account.', ['app' => config('app.name')])
|
||||
|
||||
> **@lang('Account:')** {{ $account->email }}<br/>
|
||||
> **@lang('Time:')** {{ $time->toCookieString() }}<br/>
|
||||
> **@lang('IP Address:')** {{ $ipAddress }}<br/>
|
||||
> **@lang('Browser:')** {{ $browser }}<br/>
|
||||
@if ($location && $location['default'] === false)
|
||||
> **@lang('Location:')** {{ $location['city'] ?? __('Unknown City') }}, {{ $location['state'], __('Unknown State') }}
|
||||
@endif
|
||||
|
||||
@lang('If this was you, you can ignore this alert. If you suspect any suspicious activity on your account, please change your password.')
|
||||
|
||||
@lang('Regards,')<br/>
|
||||
{{ config('app.name') }}
|
||||
@endcomponent
|
18
resources/views/emails/failedLogin.blade.php
vendored
Normal file
18
resources/views/emails/failedLogin.blade.php
vendored
Normal file
@ -0,0 +1,18 @@
|
||||
|
||||
@component('mail::message')
|
||||
@lang('notifications.hello_user', ['username' => $account->name])
|
||||
<br/><br/>
|
||||
**@lang('notifications.failed_login.resume')**<br/>
|
||||
@lang('notifications.failed_login.connection_details'):
|
||||
|
||||
<x-mail::panel>
|
||||
@lang('commons.time'): **{{ $time->toCookieString() }}**<br/>
|
||||
@lang('commons.ip_address'): **{{ $ipAddress }}**<br/>
|
||||
@lang('commons.device'): **@lang('admin.browser_on_platform', ['browser' => $browser, 'platform' => $platform])**<br/>
|
||||
</x-mail::panel>
|
||||
|
||||
@lang('notifications.failed_login.recommandations')<br/>
|
||||
|
||||
@lang('notifications.regards'),<br/>
|
||||
{{ config('app.name') }}
|
||||
@endcomponent
|
@ -19,7 +19,7 @@
|
||||
|
||||
// use App\Models\User;
|
||||
// use App\Notifications\SignedInWithNewDevice;
|
||||
// use App\Models\AuthenticationLog;
|
||||
// use App\Models\AuthLog;
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
@ -95,7 +95,7 @@
|
||||
|
||||
// Route::get('/notification', function () {
|
||||
// $user = User::find(1);
|
||||
// return (new SignedInWithNewDevice(AuthenticationLog::find(9)))
|
||||
// return (new SignedInWithNewDevice(AuthLog::find(9)))
|
||||
// ->toMail($user);
|
||||
// });
|
||||
|
||||
|
@ -523,21 +523,6 @@ public function test_demote_the_only_admin_returns_forbidden() : void
|
||||
->assertForbidden();
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function test_authLog_events_are_listened_by_authLog_listeners()
|
||||
{
|
||||
Event::fake();
|
||||
|
||||
foreach (config('authentication-log.listeners') as $type => $listenerClass) {
|
||||
Event::assertListening(
|
||||
config('authentication-log.events.' . $type),
|
||||
$listenerClass
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Local feeder because Factory cannot be used here
|
||||
*/
|
||||
|
Loading…
Reference in New Issue
Block a user