mirror of
https://github.com/Bubka/2FAuth.git
synced 2024-12-26 00:50:12 +01:00
Merge authentication-log into codebase
This commit is contained in:
parent
a3060a9ada
commit
e75589526b
57
app/Console/Commands/PurgeAuthenticationLog.php
Normal file
57
app/Console/Commands/PurgeAuthenticationLog.php
Normal file
@ -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
app/Listeners/Authentication/AccessAbstractListener.php
Normal file
42
app/Listeners/Authentication/AccessAbstractListener.php
Normal file
@ -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
app/Listeners/Authentication/AuthProxyListener.php
Normal file
81
app/Listeners/Authentication/AuthProxyListener.php
Normal file
@ -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
app/Listeners/Authentication/FailedLoginListener.php
Normal file
79
app/Listeners/Authentication/FailedLoginListener.php
Normal file
@ -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
app/Listeners/Authentication/LoginListener.php
Normal file
80
app/Listeners/Authentication/LoginListener.php
Normal file
@ -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
app/Listeners/Authentication/LogoutListener.php
Normal file
79
app/Listeners/Authentication/LogoutListener.php
Normal file
@ -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
app/Listeners/Authentication/OtherDeviceLogoutListener.php
Normal file
84
app/Listeners/Authentication/OtherDeviceLogoutListener.php
Normal file
@ -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
app/Listeners/Authentication/VisitedByProxyUserListener.php
Normal file
58
app/Listeners/Authentication/VisitedByProxyUserListener.php
Normal file
@ -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
app/Models/AuthenticationLog.php
Normal file
111
app/Models/AuthenticationLog.php
Normal file
@ -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
app/Models/Traits/AuthenticationLoggable.php
Normal file
122
app/Models/Traits/AuthenticationLoggable.php
Normal file
@ -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,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 @@
|
||||
* @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
app/Notifications/FailedLogin.php
Normal file
48
app/Notifications/FailedLogin.php
Normal file
@ -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,
|
||||
]);
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
@ -6,6 +6,9 @@
|
||||
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\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,
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
|
@ -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",
|
||||
|
307
composer.lock
generated
307
composer.lock
generated
@ -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": {
|
||||
|
@ -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 @@
|
||||
// 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 @@
|
||||
'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 @@
|
||||
'location' => false,
|
||||
|
||||
// The Notification class to send
|
||||
'template' => \Bubka\LaravelAuthenticationLog\Notifications\FailedLogin::class,
|
||||
'template' => FailedLogin::class,
|
||||
],
|
||||
],
|
||||
|
||||
@ -54,9 +62,9 @@
|
||||
|
||||
// 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,
|
||||
|
@ -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
resources/views/emails/failed.blade.php
vendored
Normal file
18
resources/views/emails/failed.blade.php
vendored
Normal file
@ -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
|
@ -19,7 +19,7 @@
|
||||
|
||||
// use App\Models\User;
|
||||
// use App\Notifications\SignedInWithNewDevice;
|
||||
// use Bubka\LaravelAuthenticationLog\Models\AuthenticationLog;
|
||||
// use App\Models\AuthenticationLog;
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
|
10
vite.config.js
vendored
10
vite.config.js
vendored
@ -78,9 +78,9 @@ export default defineConfig({
|
||||
},
|
||||
},
|
||||
},
|
||||
server: {
|
||||
watch: {
|
||||
followSymlinks: false,
|
||||
}
|
||||
}
|
||||
// server: {
|
||||
// watch: {
|
||||
// followSymlinks: false,
|
||||
// }
|
||||
// }
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user