Add logging of auth proxy user

This commit is contained in:
Bubka 2024-04-24 14:06:15 +02:00
parent e498350f62
commit 76c3b6fe0c
8 changed files with 37 additions and 18 deletions

View File

@ -16,6 +16,8 @@ use Illuminate\Validation\ValidationException;
class RemoteUserProvider implements UserProvider class RemoteUserProvider implements UserProvider
{ {
const FAKE_REMOTE_DOMAIN = '@remote';
/** /**
* The currently authenticated user. * The currently authenticated user.
* *
@ -85,7 +87,7 @@ class RemoteUserProvider implements UserProvider
*/ */
protected function fakeRemoteEmail(mixed $id) protected function fakeRemoteEmail(mixed $id)
{ {
return substr($id, 0, 184) . '@remote'; return substr($id, 0, 184) . self::FAKE_REMOTE_DOMAIN;
} }
/** /**

View File

@ -23,8 +23,7 @@ class LogUserLastSeen
// We do not track activity of: // We do not track activity of:
// - Guest // - Guest
// - User authenticated against a bearer token // - User authenticated against a bearer token
// - User authenticated via a reverse-proxy if (Auth::guard($guard)->check() && ! $request->bearerToken()) {
if (Auth::guard($guard)->check() && ! $request->bearerToken() && config('auth.defaults.guard') !== 'reverse-proxy-guard') {
Auth::guard($guard)->user()->last_seen_at = Carbon::now()->format('Y-m-d H:i:s'); Auth::guard($guard)->user()->last_seen_at = Carbon::now()->format('Y-m-d H:i:s');
Auth::guard($guard)->user()->save(); Auth::guard($guard)->user()->save();
break; break;

View File

@ -1,8 +1,9 @@
<?php <?php
namespace App\Authentication\Listeners; namespace App\Listeners\Authentication;
use App\Events\VisitedByProxyUser; use App\Events\VisitedByProxyUser;
use App\Extensions\RemoteUserProvider;
use App\Listeners\Authentication\AbstractAccessListener; use App\Listeners\Authentication\AbstractAccessListener;
use App\Notifications\SignedInWithNewDevice; use App\Notifications\SignedInWithNewDevice;
use Illuminate\Support\Carbon; use Illuminate\Support\Carbon;
@ -28,15 +29,17 @@ class VisitedByProxyUserListener extends AbstractAccessListener
$userAgent = $this->request->userAgent(); $userAgent = $this->request->userAgent();
$known = $user->authentications()->whereIpAddress($ip)->whereUserAgent($userAgent)->whereLoginSuccessful(true)->first(); $known = $user->authentications()->whereIpAddress($ip)->whereUserAgent($userAgent)->whereLoginSuccessful(true)->first();
$newUser = Carbon::parse($user->{$user->getCreatedAtColumn()})->diffInMinutes(Carbon::now()) < 1; $newUser = Carbon::parse($user->{$user->getCreatedAtColumn()})->diffInMinutes(Carbon::now()) < 1;
$guard = config('auth.defaults.guard');
$log = $user->authentications()->create([ $log = $user->authentications()->create([
'ip_address' => $ip, 'ip_address' => $ip,
'user_agent' => $userAgent, 'user_agent' => $userAgent,
'login_at' => now(), 'login_at' => now(),
'login_successful' => true, 'login_successful' => true,
'guard' => $guard,
]); ]);
if (! $known && ! $newUser && $user->preferences['notifyOnNewAuthDevice']) { if (! $known && ! $newUser && ! str_ends_with($user->email, RemoteUserProvider::FAKE_REMOTE_DOMAIN) && $user->preferences['notifyOnNewAuthDevice']) {
$user->notify(new SignedInWithNewDevice($log)); $user->notify(new SignedInWithNewDevice($log));
} }
} }

View File

@ -2,6 +2,7 @@
namespace App\Providers; namespace App\Providers;
use App\Listeners\Authentication\VisitedByProxyUserListener;
use App\Events\GroupDeleted; use App\Events\GroupDeleted;
use App\Events\GroupDeleting; use App\Events\GroupDeleting;
use App\Events\VisitedByProxyUser; use App\Events\VisitedByProxyUser;
@ -65,9 +66,9 @@ class EventServiceProvider extends ServiceProvider
Logout::class => [ Logout::class => [
LogoutListener::class, LogoutListener::class,
], ],
// VisitedByProxyUser::class => [ VisitedByProxyUser::class => [
// ProxyUserAccessListener::class, VisitedByProxyUserListener::class,
// ], ],
]; ];
/** /**

View File

@ -5,6 +5,7 @@
namespace App\Services\Auth; namespace App\Services\Auth;
use App\Events\VisitedByProxyUser;
use Illuminate\Auth\GuardHelpers; use Illuminate\Auth\GuardHelpers;
use Illuminate\Contracts\Auth\Guard; use Illuminate\Contracts\Auth\Guard;
use Illuminate\Contracts\Auth\UserProvider; use Illuminate\Contracts\Auth\UserProvider;
@ -17,14 +18,12 @@ class ReverseProxyGuard implements Guard
/** /**
* The currently authenticated user. * The currently authenticated user.
* *
* @var \Illuminate\Contracts\Auth\Authenticatable|null * @var \Illuminate\Contracts\Auth\Authenticatable|\App\Models\User|null
*/ */
protected $user; protected $user;
/** /**
* Create a new authentication guard. * Create a new authentication guard.
*
* @return void
*/ */
public function __construct(UserProvider $provider) public function __construct(UserProvider $provider)
{ {
@ -76,7 +75,13 @@ class ReverseProxyGuard implements Guard
} }
} }
return $this->user = $this->provider->retrieveById($identifier); if ($this->user = $this->provider->retrieveById($identifier)) {
if ($this->user->lastLoginAt() < now()->subMinutes(15)) {
event(new VisitedByProxyUser($this->user));
}
}
return $this->user;
} }
/** /**

View File

@ -7,6 +7,7 @@
import { UseColorMode } from '@vueuse/components' import { UseColorMode } from '@vueuse/components'
const notify = useNotifyStore() const notify = useNotifyStore()
const $2fauth = inject('2fauth')
const props = defineProps({ const props = defineProps({
userId: [Number, String], userId: [Number, String],
@ -171,6 +172,7 @@
<div> <div>
<span v-if="isFailedEntry(authentication)" v-html="$t('admin.failed_login_on', { login_at: authentication.login_at })" /> <span v-if="isFailedEntry(authentication)" v-html="$t('admin.failed_login_on', { login_at: authentication.login_at })" />
<span v-else-if="isSuccessfulLogout(authentication)" v-html="$t('admin.successful_logout_on', { login_at: authentication.logout_at })" /> <span v-else-if="isSuccessfulLogout(authentication)" v-html="$t('admin.successful_logout_on', { login_at: authentication.logout_at })" />
<span v-else-if="$2fauth.config.proxyAuth" v-html="$t('admin.viewed_on', { login_at: authentication.login_at })" />
<span v-else v-html="$t('admin.successful_login_on', { login_at: authentication.login_at })" /> <span v-else v-html="$t('admin.successful_login_on', { login_at: authentication.login_at })" />
</div> </div>
<div> <div>

View File

@ -11,6 +11,7 @@
const router = useRouter() const router = useRouter()
const user = useUserStore() const user = useUserStore()
const bus = useBusStore() const bus = useBusStore()
const $2fauth = inject('2fauth')
const isFetching = ref(false) const isFetching = ref(false)
const managedUser = ref(null) const managedUser = ref(null)
@ -212,12 +213,14 @@
<div class="block is-size-6 is-size-7-mobile has-text-grey"> <div class="block is-size-6 is-size-7-mobile has-text-grey">
{{ $t('admin.registered_on_date', { date: managedUser.info.created_at }) }} - {{ $t('admin.last_seen_on_date', { date: managedUser.info.last_seen_at }) }} {{ $t('admin.registered_on_date', { date: managedUser.info.created_at }) }} - {{ $t('admin.last_seen_on_date', { date: managedUser.info.last_seen_at }) }}
</div> </div>
<!-- isAdmin option -->
<div class="block"> <div class="block">
<!-- otp as dot -->
<FormCheckbox v-model="managedUser.info.is_admin" @update:model-value="val => saveAdminRole(val === true)" fieldName="is_admin" label="admin.forms.is_admin.label" help="admin.forms.is_admin.help" /> <FormCheckbox v-model="managedUser.info.is_admin" @update:model-value="val => saveAdminRole(val === true)" fieldName="is_admin" label="admin.forms.is_admin.label" help="admin.forms.is_admin.help" />
</div> </div>
<h2 class="title is-4 has-text-grey-light">{{ $t('admin.access') }}</h2> <h2 v-if="!$2fauth.config.proxyAuth" class="title is-4 has-text-grey-light">{{ $t('admin.access') }}</h2>
<div class="block"> <!-- access -->
<div v-if="!$2fauth.config.proxyAuth" class="block">
<!-- reset password -->
<div class="list-item is-size-6 is-size-6-mobile has-text-grey"> <div class="list-item is-size-6 is-size-6-mobile has-text-grey">
<div class="mb-3 is-flex is-justify-content-space-between"> <div class="mb-3 is-flex is-justify-content-space-between">
<div> <div>
@ -245,6 +248,7 @@
<span v-html="$t('admin.reset_password_help')" class="is-block block"></span> <span v-html="$t('admin.reset_password_help')" class="is-block block"></span>
</div> </div>
</div> </div>
<!-- personal access tokens -->
<div class="list-item is-size-6 is-size-6-mobile has-text-grey is-flex is-justify-content-space-between"> <div class="list-item is-size-6 is-size-6-mobile has-text-grey is-flex is-justify-content-space-between">
<div> <div>
<span class="has-text-weight-bold">{{ $t('settings.personal_access_tokens') }}</span> <span class="has-text-weight-bold">{{ $t('settings.personal_access_tokens') }}</span>
@ -263,6 +267,7 @@
</div> </div>
</div> </div>
</div> </div>
<!-- webauthn devices -->
<div class="list-item is-size-6 is-size-6-mobile has-text-grey is-flex is-justify-content-space-between"> <div class="list-item is-size-6 is-size-6-mobile has-text-grey is-flex is-justify-content-space-between">
<div> <div>
<span class="has-text-weight-bold">{{ $t('auth.webauthn.security_devices') }}</span> <span class="has-text-weight-bold">{{ $t('auth.webauthn.security_devices') }}</span>
@ -282,6 +287,7 @@
</div> </div>
</div> </div>
</div> </div>
<!-- last access -->
<div class="block"> <div class="block">
<h3 class="title is-5 has-text-grey-light mb-2">{{ $t('admin.last_accesses') }}</h3> <h3 class="title is-5 has-text-grey-light mb-2">{{ $t('admin.last_accesses') }}</h3>
<AccessLogViewer :userId="props.userId" :lastOnly="true" @has-more-entries="showFullLogLink = true"/> <AccessLogViewer :userId="props.userId" :lastOnly="true" @has-more-entries="showFullLogLink = true"/>

View File

@ -71,6 +71,7 @@ return [
'successful_login_on' => 'Successful login on <span class="light-or-darker">:login_at</span>', 'successful_login_on' => 'Successful login on <span class="light-or-darker">:login_at</span>',
'successful_logout_on' => 'Successful logout on <span class="light-or-darker">:login_at</span>', 'successful_logout_on' => 'Successful logout on <span class="light-or-darker">:login_at</span>',
'failed_login_on' => 'Failed login on <span class="light-or-darker">:login_at</span>', 'failed_login_on' => 'Failed login on <span class="light-or-darker">:login_at</span>',
'viewed_on' => 'Viewed on <span class="light-or-darker">:login_at</span>',
'last_accesses' => 'Last accesses', 'last_accesses' => 'Last accesses',
'see_full_log' => 'See full log', 'see_full_log' => 'See full log',
'browser_on_platform' => ':browser on :platform', 'browser_on_platform' => ':browser on :platform',