Browse Source

Complete merge with refactoring for better integration

Bubka 1 year ago
parent
commit
e498350f62

+ 6 - 0
.env.example

@@ -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 - 0
Dockerfile

@@ -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')

+ 378 - 64
_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 @@ namespace Illuminate\Support\Facades {
             /**
      * 
      *
-     * @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,95 +1821,386 @@ namespace Illuminate\Support\Facades {
                         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);
         }
                     /**
-         * Determine if the current user is authenticated. If not, throw an exception.
+         * Attempt to authenticate using HTTP Basic Auth.
          *
-         * @return \App\Models\User 
-         * @throws \Illuminate\Auth\AuthenticationException
+         * @param string $field
+         * @param array $extraConditions
+         * @return \Symfony\Component\HttpFoundation\Response|null 
+         * @throws \Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException
          * @static 
-         */        public static function authenticate()
+         */        public static function basic($field = 'email', $extraConditions = [])
         {
-                        /** @var \App\Services\Auth\ReverseProxyGuard $instance */
-                        return $instance->authenticate();
+                        /** @var \Illuminate\Auth\SessionGuard $instance */
+                        return $instance->basic($field, $extraConditions);
         }
                     /**
-         * Determine if the guard has a user instance.
+         * 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 hasUser()
+         */        public static function attempt($credentials = [], $remember = false)
         {
-                        /** @var \App\Services\Auth\ReverseProxyGuard $instance */
-                        return $instance->hasUser();
+                        /** @var \Illuminate\Auth\SessionGuard $instance */
+                        return $instance->attempt($credentials, $remember);
         }
                     /**
-         * Determine if the current user is authenticated.
+         * 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 check()
+         */        public static function attemptWhen($credentials = [], $callbacks = null, $remember = false)
         {
-                        /** @var \App\Services\Auth\ReverseProxyGuard $instance */
-                        return $instance->check();
+                        /** @var \Illuminate\Auth\SessionGuard $instance */
+                        return $instance->attemptWhen($credentials, $callbacks, $remember);
         }
                     /**
-         * Determine if the current user is a guest.
+         * 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 guest()
+         */        public static function viaRemember()
         {
-                        /** @var \App\Services\Auth\ReverseProxyGuard $instance */
-                        return $instance->guest();
+                        /** @var \Illuminate\Auth\SessionGuard $instance */
+                        return $instance->viaRemember();
         }
                     /**
-         * Get the ID for the currently authenticated user.
+         * Set the number of minutes the remember me cookie should be valid for.
          *
-         * @return int|string|null 
+         * @param int $minutes
+         * @return \Illuminate\Auth\SessionGuard 
          * @static 
-         */        public static function id()
+         */        public static function setRememberDuration($minutes)
         {
-                        /** @var \App\Services\Auth\ReverseProxyGuard $instance */
-                        return $instance->id();
+                        /** @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 \App\Services\Auth\ReverseProxyGuard 
+         * @return \Illuminate\Auth\SessionGuard 
          * @static 
          */        public static function setUser($user)
         {
-                        /** @var \App\Services\Auth\ReverseProxyGuard $instance */
+                        /** @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.
+         *
+         * @return \App\Models\User 
+         * @throws \Illuminate\Auth\AuthenticationException
+         * @static 
+         */        public static function authenticate()
+        {
+                        /** @var \Illuminate\Auth\SessionGuard $instance */
+                        return $instance->authenticate();
+        }
+                    /**
+         * Determine if the guard has a user instance.
+         *
+         * @return bool 
+         * @static 
+         */        public static function hasUser()
+        {
+                        /** @var \Illuminate\Auth\SessionGuard $instance */
+                        return $instance->hasUser();
+        }
+                    /**
+         * Determine if the current user is authenticated.
+         *
+         * @return bool 
+         * @static 
+         */        public static function check()
+        {
+                        /** @var \Illuminate\Auth\SessionGuard $instance */
+                        return $instance->check();
+        }
+                    /**
+         * Determine if the current user is a guest.
+         *
+         * @return bool 
+         * @static 
+         */        public static function guest()
+        {
+                        /** @var \Illuminate\Auth\SessionGuard $instance */
+                        return $instance->guest();
         }
                     /**
          * 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 @@ namespace Illuminate\Support\Facades {
          * @static 
          */        public static function getProvider()
         {
-                        /** @var \App\Services\Auth\ReverseProxyGuard $instance */
+                        /** @var \Illuminate\Auth\SessionGuard $instance */
                         return $instance->getProvider();
         }
                     /**
@@ -1960,8 +2221,50 @@ namespace Illuminate\Support\Facades {
          * @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 @@ namespace Illuminate\Support\Facades {
         {
                         /** @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.

+ 4 - 4
app/Console/Commands/PurgeAuthenticationLog.php → app/Console/Commands/PurgeAuthLog.php

@@ -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 @@ class PurgeAuthenticationLog extends Command
     {
         $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.');
     }

+ 1 - 1
app/Console/Commands/Utils/ResetTrait.php

@@ -70,7 +70,7 @@ trait ResetTrait
         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 - 0
app/Events/VisitedByProxyUser.php

@@ -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;
+    }
+}

+ 3 - 1
app/Listeners/Authentication/AccessAbstractListener.php → app/Listeners/Authentication/AbstractAccessListener.php

@@ -4,7 +4,7 @@ namespace App\Listeners\Authentication;
 
 use Illuminate\Http\Request;
 
-abstract class AccessAbstractListener
+abstract class AbstractAccessListener
 {
     /**
      * The current request
@@ -22,6 +22,8 @@ abstract class AccessAbstractListener
     }
 
     /**
+     * Handle the event.
+     *
      * @return void
      */
     abstract public function handle(mixed $event);

+ 0 - 81
app/Listeners/Authentication/AuthProxyListener.php

@@ -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));
-            }
-        }
-    }
-}

+ 15 - 29
app/Listeners/Authentication/FailedLoginListener.php

@@ -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 - 37
app/Listeners/Authentication/LoginListener.php

@@ -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;
-            }
-
-            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;
-            $guard     = $event->guard;
+        /**
+         * @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;
 
-            /** @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(),
-            ]);
+        $log = $user->authentications()->create([
+            'ip_address'       => $ip,
+            'user_agent'       => $userAgent,
+            'login_at'         => now(),
+            'login_successful' => true,
+            '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));
         }
     }
 }

+ 26 - 42
app/Listeners/Authentication/LogoutListener.php

@@ -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;
-            }
-
-            $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);
+        /**
+         * @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;
+
+        if (! $log) {
+            $log = new AuthLog([
+                'ip_address' => $ip,
+                'user_agent' => $userAgent,
+                'guard'      => $guard,
+            ]);
         }
+
+        $log->logout_at = now();
+        $user->authentications()->save($log);
     }
 }

+ 28 - 43
app/Listeners/Authentication/OtherDeviceLogoutListener.php

@@ -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;
-            }
-
-            $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();
-            }
+        /**
+         * @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;
 
-            $userAgent         = $this->request->userAgent();
-            $authenticationLog = $user->authentications()->whereIpAddress($ip)->whereUserAgent($userAgent)->first();
-            $guard             = $event->guard;
+        if (! $authLog) {
+            $authLog = new AuthLog([
+                'ip_address' => $ip,
+                'user_agent' => $userAgent,
+                'guard'      => $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(),
-                    ]);
-                }
-            }
         }
     }
 }

+ 26 - 41
app/Listeners/Authentication/VisitedByProxyUserListener.php

@@ -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;
         }
-
-        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));
-            }
+        
+        /**
+         * @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;
+
+        $log = $user->authentications()->create([
+            'ip_address' => $ip,
+            'user_agent' => $userAgent,
+            'login_at' => now(),
+            'login_successful' => true,
+        ]);
+
+        if (! $known && ! $newUser && $user->preferences['notifyOnNewAuthDevice']) {
+            $user->notify(new SignedInWithNewDevice($log));
         }
     }
 }

+ 2 - 30
app/Models/AuthenticationLog.php → app/Models/AuthLog.php

@@ -37,22 +37,16 @@ use Illuminate\Database\Eloquent\Relations\MorphTo;
  * @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()
     {

+ 9 - 10
app/Models/Traits/AuthenticationLoggable.php → app/Models/Traits/HasAuthenticationLog.php

@@ -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 @@ trait AuthenticationLoggable
     /**
      * 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 @@ trait AuthenticationLoggable
     /**
      * The notification channels to be used for notifications
      */
-    public function notifyAuthenticationLogVia() : array
+    public function notifyAuthLogVia() : array
     {
         return ['mail'];
     }

+ 4 - 4
app/Models/User.php

@@ -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 @@ use Laravel\Passport\HasApiTokens;
  * @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 @@ use Laravel\Passport\HasApiTokens;
  */
 class User extends Authenticatable implements HasLocalePreference, WebAuthnAuthenticatable
 {
-    use AuthenticationLoggable;
+    use HasAuthenticationLog;
     use HasApiTokens, HasFactory, Notifiable;
     use WebAuthnAuthentication, WebAuthnManageCredentials;
 

+ 25 - 12
app/Notifications/FailedLogin.php

@@ -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 @@ class FailedLogin extends Notification implements ShouldQueue
      */
     public function via(mixed $notifiable) : array|string
     {
-        return $notifiable->notifyAuthenticationLogVia();
+        return $notifiable->notifyAuthLogVia();
     }
 
     /**
@@ -36,13 +49,13 @@ class FailedLogin extends Notification implements ShouldQueue
     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(),
             ]);
     }
 }

+ 14 - 11
app/Notifications/SignedInWithNewDevice.php

@@ -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 @@ use Jenssegers\Agent\Agent;
 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 @@ class SignedInWithNewDevice extends Notification implements ShouldQueue
     {
         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 - 0
app/Providers/EventServiceProvider.php

@@ -4,6 +4,7 @@ namespace App\Providers;
 
 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,
+        // ],
     ];
 
     /**

+ 14 - 0
config/2fauth.php

@@ -28,6 +28,18 @@ return [
         '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 @@ return [
         'formatPasswordBy' => 0.5,
         'lang' => 'browser',
         'getOtpOnRequest' => true,
+        'notifyOnNewAuthDevice' => true,
+        'notifyOnFailedLogin' => true,
     ],
 
 ];

+ 0 - 71
config/authentication-log.php

@@ -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,
-];

+ 2 - 3
database/migrations/2024_04_14_082519_create_authentication_log_table.php → database/migrations/2024_04_14_082519_create_auth_logs_table.php

@@ -30,7 +30,7 @@ return new class extends Migration
 {
     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 @@ return new class extends Migration
             $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 @@ return new class extends Migration
 
     public function down(): void
     {
-        Schema::dropIfExists(config('authentication-log.table_name'));
+        Schema::dropIfExists('auth_logs');
     }
 };

+ 3 - 0
docker/docker-compose.yml

@@ -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')

+ 0 - 2
phpstan.neon

@@ -11,6 +11,4 @@ parameters:
         analyse:
             - app/Protobuf/*
     ignoreErrors:
-        -
-            message: '#.*geoip.*#'
     checkMissingIterableValueType: false

+ 6 - 0
resources/lang/en/notifications.php

@@ -27,4 +27,10 @@ return [
         '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.'
+    ],
 ];

+ 0 - 0
resources/views/emails/newDevice.blade.php → resources/views/emails/SignedInWithNewDevice.blade.php


+ 0 - 18
resources/views/emails/failed.blade.php

@@ -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 - 0
resources/views/emails/failedLogin.blade.php

@@ -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

+ 2 - 2
routes/web.php

@@ -19,7 +19,7 @@ use Laravel\Passport\Http\Controllers\PersonalAccessTokenController;
 
 // use App\Models\User;
 // use App\Notifications\SignedInWithNewDevice;
-// use App\Models\AuthenticationLog;
+// use App\Models\AuthLog;
 
 /*
 |--------------------------------------------------------------------------
@@ -95,7 +95,7 @@ Route::get('refresh-csrf', function () {
 
 // Route::get('/notification', function () {
 //     $user = User::find(1);
-//     return (new SignedInWithNewDevice(AuthenticationLog::find(9)))
+//     return (new SignedInWithNewDevice(AuthLog::find(9)))
 //         ->toMail($user);
 // });
 

+ 0 - 15
tests/Api/v1/Controllers/UserManagerControllerTest.php

@@ -523,21 +523,6 @@ class UserManagerControllerTest extends FeatureTestCase
             ->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
      */