Browse Source

Merge authentication-log into codebase

Bubka 1 year ago
parent
commit
e75589526b

+ 57 - 0
app/Console/Commands/PurgeAuthenticationLog.php

@@ -0,0 +1,57 @@
+<?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\Console\Commands;
+
+use App\Models\AuthenticationLog;
+use Illuminate\Console\Command;
+
+class PurgeAuthenticationLog extends Command
+{
+    /**
+     * The name and signature of the console command.
+     *
+     * @var string
+     */
+    public $signature = 'authentication-log:purge';
+
+    /**
+     * The console command description.
+     *
+     * @var string
+     */
+    public $description = 'Purge all authentication logs older than the configurable amount of days.';
+
+    /**
+     * Execute the console command.
+     */
+    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();
+
+        $this->info($deleted . ' authentication logs cleared.');
+    }
+}

+ 42 - 0
app/Listeners/Authentication/AccessAbstractListener.php

@@ -0,0 +1,42 @@
+<?php
+
+namespace App\Listeners\Authentication;
+
+use Illuminate\Http\Request;
+
+abstract class AccessAbstractListener
+{
+    /**
+     * The current request
+     */
+    public Request $request;
+
+    /**
+     * Create the event listener.
+     *
+     * @return void
+     */
+    public function __construct(Request $request)
+    {
+        $this->request = $request;
+    }
+
+    /**
+     * @return void
+     */
+    abstract public function handle(mixed $event);
+
+    /**
+     * Get the login method based on the request input parameters
+    */
+    public function loginMethod() : ?string
+    {
+        if ($this->request->has('response.authenticatorData')) {
+            return 'webauthn';
+        } elseif ($this->request->has('password')) {
+            return 'password';
+        } else {
+            return null;
+        }
+    }
+}

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

@@ -0,0 +1,81 @@
+<?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));
+            }
+        }
+    }
+}

+ 79 - 0
app/Listeners/Authentication/FailedLoginListener.php

@@ -0,0 +1,79 @@
+<?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 App\Notifications\FailedLogin;
+use App\Models\Traits\AuthenticationLoggable;
+use Illuminate\Auth\Events\Failed;
+use Illuminate\Http\Request;
+
+class FailedLoginListener extends AccessAbstractListener
+{
+    public Request $request;
+
+    public function __construct(Request $request)
+    {
+        $this->request = $request;
+    }
+
+    public function handle(mixed $event) : void
+    {
+        $listener = config('authentication-log.events.failed', Failed::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();
+            }
+
+            $guard = $event->guard;
+
+            /** @disregard Undefined function */
+            $log = $event->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));
+            }
+        }
+    }
+}

+ 80 - 0
app/Listeners/Authentication/LoginListener.php

@@ -0,0 +1,80 @@
+<?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 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
+{
+    /**
+     * 
+     */
+    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;
+            $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));
+            }
+        }
+    }
+}

+ 79 - 0
app/Listeners/Authentication/LogoutListener.php

@@ -0,0 +1,79 @@
+<?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 App\Models\AuthenticationLog;
+use App\Models\Traits\AuthenticationLoggable;
+use Illuminate\Auth\Events\Logout;
+use Illuminate\Http\Request;
+
+class LogoutListener extends AccessAbstractListener
+{
+    public Request $request;
+
+    public function __construct(Request $request)
+    {
+        $this->request = $request;
+    }
+
+    public function handle(mixed $event) : void
+    {
+        $listener = config('authentication-log.events.logout', Logout::class);
+
+        if (! $event instanceof $listener) {
+            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);
+        }
+    }
+}

+ 84 - 0
app/Listeners/Authentication/OtherDeviceLogoutListener.php

@@ -0,0 +1,84 @@
+<?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 App\Models\AuthenticationLog;
+use App\Models\Traits\AuthenticationLoggable;
+use Illuminate\Auth\Events\OtherDeviceLogout;
+use Illuminate\Http\Request;
+
+class OtherDeviceLogoutListener extends AccessAbstractListener
+{
+    public Request $request;
+
+    public function __construct(Request $request)
+    {
+        $this->request = $request;
+    }
+
+    public function handle(mixed $event) : void
+    {
+        $listener = config('authentication-log.events.other-device-logout', OtherDeviceLogout::class);
+
+        if (! $event instanceof $listener) {
+            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();
+            $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 !== $authenticationLog->id) {
+                    $log->update([
+                        'cleared_by_user' => true,
+                        'logout_at'       => now(),
+                    ]);
+                }
+            }
+        }
+    }
+}

+ 58 - 0
app/Listeners/Authentication/VisitedByProxyUserListener.php

@@ -0,0 +1,58 @@
+<?php
+
+namespace App\Authentication\Listeners;
+
+use App\Events\VisitedByProxyUser;
+use App\Listeners\Authentication\AccessAbstractListener;
+use App\Models\Traits\AuthenticationLoggable;
+use App\Notifications\SignedInWithNewDevice;
+use Illuminate\Http\Request;
+use Illuminate\Support\Carbon;
+
+class VisitedByProxyUserListener extends AccessAbstractListener
+{
+    public Request $request;
+
+    public function __construct(Request $request)
+    {
+        $this->request = $request;
+    }
+
+    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));
+            }
+        }
+    }
+}

+ 111 - 0
app/Models/AuthenticationLog.php

@@ -0,0 +1,111 @@
+<?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\Models;
+
+use Illuminate\Database\Eloquent\Model;
+use Illuminate\Database\Eloquent\Relations\MorphTo;
+
+/**
+ * @property int $id
+ * @property string $authenticatable_type
+ * @property int $authenticatable_id
+ * @property string|null $ip_address
+ * @property string|null $user_agent
+ * @property \Illuminate\Support\Carbon|null $login_at
+ * @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
+{
+    /**
+     * 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.
+     */
+    protected $fillable = [
+        'ip_address',
+        'user_agent',
+        'login_at',
+        'login_successful',
+        'logout_at',
+        'cleared_by_user',
+        'location',
+        'guard',
+        'login_method',
+    ];
+
+    /**
+     * The attributes that should be cast.
+     */
+    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>
+     */
+    public function authenticatable()
+    {
+        return $this->morphTo();
+    }
+}

+ 122 - 0
app/Models/Traits/AuthenticationLoggable.php

@@ -0,0 +1,122 @@
+<?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\Models\Traits;
+
+use App\Models\AuthenticationLog;
+use Illuminate\Support\Carbon;
+use Illuminate\Support\Collection;
+
+trait AuthenticationLoggable
+{
+    /**
+     * Get all user's authentications from the auth log
+     *
+     * @return \Illuminate\Database\Eloquent\Relations\MorphMany<AuthenticationLog>
+     */
+    public function authentications()
+    {
+        return $this->morphMany(AuthenticationLog::class, 'authenticatable')->latest('id');
+    }
+
+    /**
+     * Get authentications for the provided timespan (in month)
+     *
+     * @return \Illuminate\Database\Eloquent\Collection<int, AuthenticationLog>
+     */
+    public function authenticationsByPeriod(int $period = 1)
+    {
+        $from = Carbon::now()->subMonths($period);
+
+        return $this->authentications->filter(function (AuthenticationLog $authentication) use ($from) {
+            return $authentication->login_at >= $from || $authentication->logout_at >= $from;
+        });
+    }
+
+    /**
+     * Get the user's latest authentication
+     * 
+     * @return \Illuminate\Database\Eloquent\Relations\MorphOne<AuthenticationLog>
+     */
+    public function latestAuthentication()
+    {
+        return $this->morphOne(AuthenticationLog::class, 'authenticatable')->latestOfMany('login_at');
+    }
+
+    /**
+     * Get the user's latest authentication datetime
+     */
+    public function lastLoginAt() : ?Carbon
+    {
+        return $this->authentications()->first()?->login_at;
+    }
+
+    /**
+     * Get the user's latest successful login datetime
+     */
+    public function lastSuccessfulLoginAt() : ?Carbon
+    {
+        return $this->authentications()->whereLoginSuccessful(true)->first()?->login_at;
+    }
+
+    /**
+     * Get the ip address of user's latest login
+     */
+    public function lastLoginIp() : ?string
+    {
+        return $this->authentications()->first()?->ip_address;
+    }
+
+    /**
+     * Get the ip address of user's latest successful login
+     */
+    public function lastSuccessfulLoginIp() : ?string
+    {
+        return $this->authentications()->whereLoginSuccessful(true)->first()?->ip_address;
+    }
+
+    /**
+     * Get the user's previous login datetime
+     */
+    public function previousLoginAt() : ?Carbon
+    {
+        return $this->authentications()->skip(1)->first()?->login_at;
+    }
+
+    /**
+     * Get the ip address of user's previous login
+     */
+    public function previousLoginIp() : ?string
+    {
+        return $this->authentications()->skip(1)->first()?->ip_address;
+    }
+
+    /**
+     * The notification channels to be used for notifications
+     */
+    public function notifyAuthenticationLogVia() : array
+    {
+        return ['mail'];
+    }
+}

+ 3 - 3
app/Models/User.php

@@ -3,7 +3,7 @@
 namespace App\Models;
 
 use App\Models\Traits\WebAuthnManageCredentials;
-use Bubka\LaravelAuthenticationLog\Traits\AuthenticationLoggable;
+use App\Models\Traits\AuthenticationLoggable;
 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, \Bubka\LaravelAuthenticationLog\Models\AuthenticationLog> $authentications
+ * @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\AuthenticationLog> $authentications
  * @property-read int|null $authentications_count
- * @property-read \Bubka\LaravelAuthenticationLog\Models\AuthenticationLog|null $latestAuthentication
+ * @property-read \App\Models\AuthenticationLog|null $latestAuthentication
  *
  * @method static \Illuminate\Database\Eloquent\Builder|User admins()
  *

+ 48 - 0
app/Notifications/FailedLogin.php

@@ -0,0 +1,48 @@
+<?php
+
+namespace App\Notifications;
+
+use App\Models\AuthenticationLog;
+use Illuminate\Bus\Queueable;
+use Illuminate\Contracts\Queue\ShouldQueue;
+use Illuminate\Notifications\Messages\MailMessage;
+use Illuminate\Notifications\Notification;
+
+class FailedLogin extends Notification implements ShouldQueue
+{
+    use Queueable;
+
+    /**
+     * The AuthenticationLog model instance
+     */
+    public AuthenticationLog $authenticationLog;
+
+    public function __construct(AuthenticationLog $authenticationLog)
+    {
+        $this->authenticationLog = $authenticationLog;
+    }
+
+    /**
+     * Get the notification's channels.
+     */
+    public function via(mixed $notifiable) : array|string
+    {
+        return $notifiable->notifyAuthenticationLogVia();
+    }
+
+    /**
+     * Build the mail representation of the notification.
+     */
+    public function toMail(mixed $notifiable) : MailMessage
+    {
+        return (new MailMessage())
+            ->subject(__('A failed login to your account'))
+            ->markdown('authentication-log::emails.failed', [
+                'account'   => $notifiable,
+                'time'      => $this->authenticationLog->login_at,
+                'ipAddress' => $this->authenticationLog->ip_address,
+                'browser'   => $this->authenticationLog->user_agent,
+                'location'  => $this->authenticationLog->location,
+            ]);
+    }
+}

+ 1 - 1
app/Notifications/SignedInWithNewDevice.php

@@ -2,7 +2,7 @@
 
 namespace App\Notifications;
 
-use Bubka\LaravelAuthenticationLog\Models\AuthenticationLog;
+use App\Models\AuthenticationLog;
 use Illuminate\Bus\Queueable;
 use Illuminate\Contracts\Queue\ShouldQueue;
 use Illuminate\Notifications\Messages\MailMessage;

+ 15 - 0
app/Providers/EventServiceProvider.php

@@ -6,6 +6,9 @@ use App\Events\GroupDeleted;
 use App\Events\GroupDeleting;
 use App\Events\ScanForNewReleaseCalled;
 use App\Events\TwoFAccountDeleted;
+use App\Listeners\Authentication\FailedLoginListener;
+use App\Listeners\Authentication\LoginListener;
+use App\Listeners\Authentication\LogoutListener;
 use App\Listeners\CleanIconStorage;
 use App\Listeners\DissociateTwofaccountFromGroup;
 use App\Listeners\LogNotification;
@@ -14,6 +17,9 @@ use App\Listeners\ReleaseRadar;
 use App\Listeners\ResetUsersPreference;
 use App\Models\User;
 use App\Observers\UserObserver;
+use Illuminate\Auth\Events\Failed;
+use Illuminate\Auth\Events\Login;
+use Illuminate\Auth\Events\Logout;
 use Illuminate\Auth\Events\Registered;
 use Illuminate\Auth\Listeners\SendEmailVerificationNotification;
 use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
@@ -49,6 +55,15 @@ class EventServiceProvider extends ServiceProvider
         NotificationSent::class => [
             LogNotification::class,
         ],
+        Login::class => [
+            LoginListener::class,
+        ],
+        Failed::class => [
+            FailedLoginListener::class,
+        ],
+        Logout::class => [
+            LogoutListener::class,
+        ],
     ];
 
     /**

+ 1 - 11
composer.json

@@ -37,18 +37,8 @@
         "paragonie/constant_time_encoding": "^2.6",
         "socialiteproviders/manager": "^4.4",
         "spatie/eloquent-sortable": "^4.0.1",
-        "spomky-labs/otphp": "^11.0",
-        "bubka/laravel-authentication-log": "@dev"
+        "spomky-labs/otphp": "^11.0"
     },
-    "repositories": [
-        {
-            "type": "path",
-            "url": "../packages/bubka/laravel-authentication-log",
-            "options": {
-                "symlink": true
-            }
-        }
-    ],
     "require-dev": {
         "barryvdh/laravel-ide-helper": "^2.13",
         "brianium/paratest": "^7.3",

+ 126 - 181
composer.lock

@@ -4,7 +4,7 @@
         "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
         "This file is @generated automatically"
     ],
-    "content-hash": "3d355aab37adc72f3901f200edcf2445",
+    "content-hash": "93e717902e3c1435d0768115a200b000",
     "packages": [
         {
             "name": "brick/math",
@@ -61,59 +61,6 @@
             ],
             "time": "2023-01-15T23:15:59+00:00"
         },
-        {
-            "name": "bubka/laravel-authentication-log",
-            "version": "dev-main",
-            "dist": {
-                "type": "path",
-                "url": "../packages/bubka/laravel-authentication-log",
-                "reference": "a916caaa979b1d18d679d8b063325fe547f691c6"
-            },
-            "require": {
-                "illuminate/contracts": "^10.0|^11.0",
-                "laravel/pint": "^1.15",
-                "php": "^8.1",
-                "phpstan/phpstan": "^1.10",
-                "spatie/laravel-package-tools": "^1.4.3"
-            },
-            "require-dev": {
-                "larastan/larastan": "^2.9",
-                "nunomaduro/collision": "^6.0",
-                "orchestra/testbench": "^8.22"
-            },
-            "type": "library",
-            "extra": {
-                "laravel": {
-                    "providers": [
-                        "Bubka\\LaravelAuthenticationLog\\LaravelAuthenticationLogServiceProvider"
-                    ]
-                }
-            },
-            "autoload": {
-                "psr-4": {
-                    "Bubka\\LaravelAuthenticationLog\\": "src",
-                    "Bubka\\LaravelAuthenticationLog\\Database\\Factories\\": "database/factories"
-                }
-            },
-            "license": [
-                "MIT"
-            ],
-            "authors": [
-                {
-                    "name": "Bubka"
-                }
-            ],
-            "description": "Log user authentication details and send new device notifications.",
-            "homepage": "https://github.com/bubka/laravel-authentication-log",
-            "keywords": [
-                "laravel",
-                "laravel-authentication-log"
-            ],
-            "transport-options": {
-                "symlink": true,
-                "relative": true
-            }
-        },
         {
             "name": "carbonphp/carbon-doctrine-types",
             "version": "2.1.0",
@@ -2543,72 +2490,6 @@
             },
             "time": "2024-03-01T11:11:18+00:00"
         },
-        {
-            "name": "laravel/pint",
-            "version": "v1.15.1",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/laravel/pint.git",
-                "reference": "5f288b5e79938cc72f5c298d384e639de87507c6"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/laravel/pint/zipball/5f288b5e79938cc72f5c298d384e639de87507c6",
-                "reference": "5f288b5e79938cc72f5c298d384e639de87507c6",
-                "shasum": ""
-            },
-            "require": {
-                "ext-json": "*",
-                "ext-mbstring": "*",
-                "ext-tokenizer": "*",
-                "ext-xml": "*",
-                "php": "^8.1.0"
-            },
-            "require-dev": {
-                "friendsofphp/php-cs-fixer": "^3.52.1",
-                "illuminate/view": "^10.48.4",
-                "larastan/larastan": "^2.9.2",
-                "laravel-zero/framework": "^10.3.0",
-                "mockery/mockery": "^1.6.11",
-                "nunomaduro/termwind": "^1.15.1",
-                "pestphp/pest": "^2.34.5"
-            },
-            "bin": [
-                "builds/pint"
-            ],
-            "type": "project",
-            "autoload": {
-                "psr-4": {
-                    "App\\": "app/",
-                    "Database\\Seeders\\": "database/seeders/",
-                    "Database\\Factories\\": "database/factories/"
-                }
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "MIT"
-            ],
-            "authors": [
-                {
-                    "name": "Nuno Maduro",
-                    "email": "enunomaduro@gmail.com"
-                }
-            ],
-            "description": "An opinionated code formatter for PHP.",
-            "homepage": "https://laravel.com",
-            "keywords": [
-                "format",
-                "formatter",
-                "lint",
-                "linter",
-                "php"
-            ],
-            "support": {
-                "issues": "https://github.com/laravel/pint/issues",
-                "source": "https://github.com/laravel/pint"
-            },
-            "time": "2024-04-02T14:28:47+00:00"
-        },
         {
             "name": "laravel/prompts",
             "version": "v0.1.19",
@@ -4791,64 +4672,6 @@
             ],
             "time": "2024-03-03T02:14:58+00:00"
         },
-        {
-            "name": "phpstan/phpstan",
-            "version": "1.10.67",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/phpstan/phpstan.git",
-                "reference": "16ddbe776f10da6a95ebd25de7c1dbed397dc493"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/phpstan/phpstan/zipball/16ddbe776f10da6a95ebd25de7c1dbed397dc493",
-                "reference": "16ddbe776f10da6a95ebd25de7c1dbed397dc493",
-                "shasum": ""
-            },
-            "require": {
-                "php": "^7.2|^8.0"
-            },
-            "conflict": {
-                "phpstan/phpstan-shim": "*"
-            },
-            "bin": [
-                "phpstan",
-                "phpstan.phar"
-            ],
-            "type": "library",
-            "autoload": {
-                "files": [
-                    "bootstrap.php"
-                ]
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "MIT"
-            ],
-            "description": "PHPStan - PHP Static Analysis Tool",
-            "keywords": [
-                "dev",
-                "static analysis"
-            ],
-            "support": {
-                "docs": "https://phpstan.org/user-guide/getting-started",
-                "forum": "https://github.com/phpstan/phpstan/discussions",
-                "issues": "https://github.com/phpstan/phpstan/issues",
-                "security": "https://github.com/phpstan/phpstan/security/policy",
-                "source": "https://github.com/phpstan/phpstan-src"
-            },
-            "funding": [
-                {
-                    "url": "https://github.com/ondrejmirtes",
-                    "type": "github"
-                },
-                {
-                    "url": "https://github.com/phpstan",
-                    "type": "github"
-                }
-            ],
-            "time": "2024-04-16T07:22:02+00:00"
-        },
         {
             "name": "psr/cache",
             "version": "3.0.0",
@@ -9285,6 +9108,72 @@
             ],
             "time": "2024-04-16T19:13:34+00:00"
         },
+        {
+            "name": "laravel/pint",
+            "version": "v1.15.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/laravel/pint.git",
+                "reference": "5f288b5e79938cc72f5c298d384e639de87507c6"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/laravel/pint/zipball/5f288b5e79938cc72f5c298d384e639de87507c6",
+                "reference": "5f288b5e79938cc72f5c298d384e639de87507c6",
+                "shasum": ""
+            },
+            "require": {
+                "ext-json": "*",
+                "ext-mbstring": "*",
+                "ext-tokenizer": "*",
+                "ext-xml": "*",
+                "php": "^8.1.0"
+            },
+            "require-dev": {
+                "friendsofphp/php-cs-fixer": "^3.52.1",
+                "illuminate/view": "^10.48.4",
+                "larastan/larastan": "^2.9.2",
+                "laravel-zero/framework": "^10.3.0",
+                "mockery/mockery": "^1.6.11",
+                "nunomaduro/termwind": "^1.15.1",
+                "pestphp/pest": "^2.34.5"
+            },
+            "bin": [
+                "builds/pint"
+            ],
+            "type": "project",
+            "autoload": {
+                "psr-4": {
+                    "App\\": "app/",
+                    "Database\\Seeders\\": "database/seeders/",
+                    "Database\\Factories\\": "database/factories/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Nuno Maduro",
+                    "email": "enunomaduro@gmail.com"
+                }
+            ],
+            "description": "An opinionated code formatter for PHP.",
+            "homepage": "https://laravel.com",
+            "keywords": [
+                "format",
+                "formatter",
+                "lint",
+                "linter",
+                "php"
+            ],
+            "support": {
+                "issues": "https://github.com/laravel/pint/issues",
+                "source": "https://github.com/laravel/pint"
+            },
+            "time": "2024-04-02T14:28:47+00:00"
+        },
         {
             "name": "mockery/mockery",
             "version": "1.6.11",
@@ -9887,6 +9776,64 @@
             },
             "time": "2024-04-03T18:51:33+00:00"
         },
+        {
+            "name": "phpstan/phpstan",
+            "version": "1.10.67",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/phpstan/phpstan.git",
+                "reference": "16ddbe776f10da6a95ebd25de7c1dbed397dc493"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/phpstan/phpstan/zipball/16ddbe776f10da6a95ebd25de7c1dbed397dc493",
+                "reference": "16ddbe776f10da6a95ebd25de7c1dbed397dc493",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^7.2|^8.0"
+            },
+            "conflict": {
+                "phpstan/phpstan-shim": "*"
+            },
+            "bin": [
+                "phpstan",
+                "phpstan.phar"
+            ],
+            "type": "library",
+            "autoload": {
+                "files": [
+                    "bootstrap.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "description": "PHPStan - PHP Static Analysis Tool",
+            "keywords": [
+                "dev",
+                "static analysis"
+            ],
+            "support": {
+                "docs": "https://phpstan.org/user-guide/getting-started",
+                "forum": "https://github.com/phpstan/phpstan/discussions",
+                "issues": "https://github.com/phpstan/phpstan/issues",
+                "security": "https://github.com/phpstan/phpstan/security/policy",
+                "source": "https://github.com/phpstan/phpstan-src"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/ondrejmirtes",
+                    "type": "github"
+                },
+                {
+                    "url": "https://github.com/phpstan",
+                    "type": "github"
+                }
+            ],
+            "time": "2024-04-16T07:22:02+00:00"
+        },
         {
             "name": "phpunit/php-code-coverage",
             "version": "10.1.14",
@@ -11584,9 +11531,7 @@
     ],
     "aliases": [],
     "minimum-stability": "stable",
-    "stability-flags": {
-        "bubka/laravel-authentication-log": 20
-    },
+    "stability-flags": [],
     "prefer-stable": true,
     "prefer-lowest": false,
     "platform": {

+ 30 - 22
config/authentication-log.php

@@ -1,5 +1,30 @@
 <?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
@@ -8,23 +33,6 @@ return [
     // The database connection where the authentication_log table resides. Leave empty to use the default
     'db_connection' => null,
 
-    // The events the package listens for to log
-    'events' => [
-        'login' => \Illuminate\Auth\Events\Login::class,
-        'failed' => \Illuminate\Auth\Events\Failed::class,
-        'logout' => \Illuminate\Auth\Events\Logout::class,
-        // 'logout-other-devices' => \Illuminate\Auth\Events\OtherDeviceLogout::class,
-        // 'proxyUserAccess' => \App\Events\VisitedByProxyUser::class,
-    ],
-
-    'listeners' => [
-        'login' => \Bubka\LaravelAuthenticationLog\Listeners\LoginListener::class,
-        'failed' => \Bubka\LaravelAuthenticationLog\Listeners\FailedLoginListener::class,
-        'logout' => \Bubka\LaravelAuthenticationLog\Listeners\LogoutListener::class,
-        // 'logout-other-devices' => \Bubka\LaravelAuthenticationLog\Listeners\OtherDeviceLogoutListener::class,
-        // 'proxyUserAccess' => \App\Listeners\VisitedByProxyUserListener::class,
-    ],
-
     'notifications' => [
         'new-device' => [
             // Send the NewDevice notification
@@ -34,7 +42,7 @@ return [
             'location' => false,
 
             // The Notification class to send
-            'template' => \App\Notifications\SignedInWithNewDevice::class,
+            'template' => SignedInWithNewDevice::class,
         ],
         'failed-login' => [
             // Send the FailedLogin notification
@@ -44,7 +52,7 @@ return [
             'location' => false,
 
             // The Notification class to send
-            'template' => \Bubka\LaravelAuthenticationLog\Notifications\FailedLogin::class,
+            'template' => FailedLogin::class,
         ],
     ],
 
@@ -54,9 +62,9 @@ return [
 
     // 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
-//    ],
+    // 'behind_cdn' => [
+    //     'http_header_field' => 'HTTP_CF_CONNECTING_IP' // used by Cloudflare
+    // ],
 
     // If you are not a cdn user, use false
     'behind_cdn' => false,

+ 22 - 0
database/migrations/2024_04_14_082519_create_authentication_log_table.php

@@ -1,5 +1,27 @@
 <?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 Illuminate\Database\Migrations\Migration;
 use Illuminate\Database\Schema\Blueprint;
 use Illuminate\Support\Facades\Schema;

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

@@ -0,0 +1,18 @@
+@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

+ 1 - 1
routes/web.php

@@ -19,7 +19,7 @@ use Laravel\Passport\Http\Controllers\PersonalAccessTokenController;
 
 // use App\Models\User;
 // use App\Notifications\SignedInWithNewDevice;
-// use Bubka\LaravelAuthenticationLog\Models\AuthenticationLog;
+// use App\Models\AuthenticationLog;
 
 /*
 |--------------------------------------------------------------------------

+ 5 - 5
vite.config.js

@@ -78,9 +78,9 @@ export default defineConfig({
             },
         },
     },
-    server: {
-        watch: {
-            followSymlinks: false,
-        }
-    }
+    // server: {
+    //     watch: {
+    //         followSymlinks: false,
+    //     }
+    // }
 });