Skip to content

Commit 0999ad7

Browse files
committed
Add activity logging for authentication events
1 parent 5bb66a0 commit 0999ad7

File tree

11 files changed

+179
-18
lines changed

11 files changed

+179
-18
lines changed
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?php
2+
3+
namespace Pterodactyl\Events\Auth;
4+
5+
use Pterodactyl\Models\User;
6+
7+
class ProvidedAuthenticationToken
8+
{
9+
public User $user;
10+
11+
public bool $recovery;
12+
13+
public function __construct(User $user, bool $recovery = false)
14+
{
15+
$this->user = $user;
16+
$this->recovery = $recovery;
17+
}
18+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php
2+
3+
namespace Pterodactyl\Extensions\Illuminate\Events\Contracts;
4+
5+
use Illuminate\Contracts\Events\Dispatcher;
6+
7+
interface SubscribesToEvents
8+
{
9+
public function subscribe(Dispatcher $events): void;
10+
}

app/Facades/Activity.php

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,14 @@
99
/**
1010
* @method static ActivityLogService anonymous()
1111
* @method static ActivityLogService event(string $action)
12-
* @method static ActivityLogService withDescription(?string $description)
13-
* @method static ActivityLogService withSubject(Model $subject)
14-
* @method static ActivityLogService withActor(Model $actor)
12+
* @method static ActivityLogService description(?string $description)
13+
* @method static ActivityLogService subject(Model $subject)
14+
* @method static ActivityLogService actor(Model $actor)
1515
* @method static ActivityLogService withProperties(\Illuminate\Support\Collection|array $properties)
16-
* @method static ActivityLogService withProperty(string $key, mixed $value)
16+
* @method static ActivityLogService withRequestMetadata()
17+
* @method static ActivityLogService property(string $key, mixed $value)
18+
* @method static \Pterodactyl\Models\ActivityLog log(string $description = null)
19+
* @method static ActivityLogService clone()
1720
* @method static mixed transaction(\Closure $callback)
1821
*/
1922
class Activity extends Facade

app/Http/Controllers/Auth/LoginCheckpointController.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,10 @@
77
use Pterodactyl\Models\User;
88
use Illuminate\Http\JsonResponse;
99
use PragmaRX\Google2FA\Google2FA;
10+
use Illuminate\Support\Facades\Event;
1011
use Illuminate\Contracts\Encryption\Encrypter;
1112
use Illuminate\Database\Eloquent\ModelNotFoundException;
13+
use Pterodactyl\Events\Auth\ProvidedAuthenticationToken;
1214
use Pterodactyl\Http\Requests\Auth\LoginCheckpointRequest;
1315
use Illuminate\Contracts\Validation\Factory as ValidationFactory;
1416

@@ -72,12 +74,16 @@ public function __invoke(LoginCheckpointRequest $request): JsonResponse
7274
// Recovery tokens go through a slightly different pathway for usage.
7375
if (!is_null($recoveryToken = $request->input('recovery_token'))) {
7476
if ($this->isValidRecoveryToken($user, $recoveryToken)) {
77+
Event::dispatch(new ProvidedAuthenticationToken($user, true));
78+
7579
return $this->sendLoginResponse($user, $request);
7680
}
7781
} else {
7882
$decrypted = $this->encrypter->decrypt($user->totp_secret);
7983

8084
if ($this->google2FA->verifyKey($decrypted, (string) $request->input('authentication_code') ?? '', config('pterodactyl.auth.2fa.window'))) {
85+
Event::dispatch(new ProvidedAuthenticationToken($user));
86+
8187
return $this->sendLoginResponse($user, $request);
8288
}
8389
}

app/Http/Controllers/Auth/LoginController.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use Illuminate\Http\Request;
88
use Pterodactyl\Models\User;
99
use Illuminate\Http\JsonResponse;
10+
use Pterodactyl\Facades\Activity;
1011
use Illuminate\Contracts\View\View;
1112
use Illuminate\Contracts\View\Factory as ViewFactory;
1213
use Illuminate\Database\Eloquent\ModelNotFoundException;
@@ -71,6 +72,8 @@ public function login(Request $request): JsonResponse
7172
return $this->sendLoginResponse($user, $request);
7273
}
7374

75+
Activity::event('login.checkpoint')->withRequestMetadata()->subject($user)->log();
76+
7477
$request->session()->put('auth_confirmation_token', [
7578
'user_id' => $user->id,
7679
'token_value' => $token = Str::random(64),
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<?php
2+
3+
namespace Pterodactyl\Listeners\Auth;
4+
5+
use Pterodactyl\Facades\Activity;
6+
use Illuminate\Auth\Events\Login;
7+
use Illuminate\Auth\Events\Failed;
8+
use Illuminate\Contracts\Events\Dispatcher;
9+
use Pterodactyl\Extensions\Illuminate\Events\Contracts\SubscribesToEvents;
10+
11+
class AuthenticationListener implements SubscribesToEvents
12+
{
13+
/**
14+
* Handles an authentication event by logging the user and information about
15+
* the request.
16+
*
17+
* @param \Illuminate\Auth\Events\Login|\Illuminate\Auth\Events\Failed $event
18+
*/
19+
public function handle($event): void
20+
{
21+
$activity = Activity::withRequestMetadata();
22+
if ($event->user) {
23+
$activity = $activity->subject($event->user);
24+
}
25+
26+
if ($event instanceof Failed) {
27+
foreach ($event->credentials as $key => $value) {
28+
$activity = $activity->property($key, $value);
29+
}
30+
}
31+
32+
$activity->event($event instanceof Failed ? 'login.failed' : 'login.success')->log();
33+
}
34+
35+
public function subscribe(Dispatcher $events): void
36+
{
37+
$events->listen(Failed::class, self::class);
38+
$events->listen(Login::class, self::class);
39+
}
40+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?php
2+
3+
namespace Pterodactyl\Listeners\Auth;
4+
5+
use Illuminate\Http\Request;
6+
use Pterodactyl\Facades\Activity;
7+
use Illuminate\Auth\Events\PasswordReset;
8+
9+
class PasswordResetListener
10+
{
11+
protected Request $request;
12+
13+
public function __construct(Request $request)
14+
{
15+
$this->request = $request;
16+
}
17+
18+
public function handle(PasswordReset $event)
19+
{
20+
Activity::event('login.password-reset')
21+
->withRequestMetadata()
22+
->subject($event->user)
23+
->log();
24+
}
25+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php
2+
3+
namespace Pterodactyl\Listeners\Auth;
4+
5+
use Pterodactyl\Facades\Activity;
6+
use Pterodactyl\Events\Auth\ProvidedAuthenticationToken;
7+
8+
class TwoFactorListener
9+
{
10+
public function handle(ProvidedAuthenticationToken $event)
11+
{
12+
Activity::event($event->recovery ? 'login.recovery-token' : 'login.token')
13+
->withRequestMetadata()
14+
->subject($event->user)
15+
->log();
16+
}
17+
}

app/Models/User.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace Pterodactyl\Models;
44

55
use Pterodactyl\Rules\Username;
6+
use Pterodactyl\Facades\Activity;
67
use Illuminate\Support\Collection;
78
use Illuminate\Validation\Rules\In;
89
use Illuminate\Auth\Authenticatable;
@@ -214,6 +215,11 @@ public function toVueObject(): array
214215
*/
215216
public function sendPasswordResetNotification($token)
216217
{
218+
Activity::event('login.reset-password')
219+
->withRequestMetadata()
220+
->subject($this)
221+
->log('sending password reset email');
222+
217223
$this->notify(new ResetPasswordNotification($token));
218224
}
219225

app/Providers/EventServiceProvider.php

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
use Pterodactyl\Observers\ServerObserver;
1111
use Pterodactyl\Observers\SubuserObserver;
1212
use Pterodactyl\Observers\EggVariableObserver;
13+
use Pterodactyl\Listeners\Auth\AuthenticationListener;
1314
use Pterodactyl\Events\Server\Installed as ServerInstalledEvent;
1415
use Pterodactyl\Notifications\ServerInstalled as ServerInstalledNotification;
1516
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
@@ -22,9 +23,11 @@ class EventServiceProvider extends ServiceProvider
2223
* @var array
2324
*/
2425
protected $listen = [
25-
ServerInstalledEvent::class => [
26-
ServerInstalledNotification::class,
27-
],
26+
ServerInstalledEvent::class => [ServerInstalledNotification::class],
27+
];
28+
29+
protected $subscribe = [
30+
AuthenticationListener::class,
2831
];
2932

3033
/**
@@ -39,4 +42,9 @@ public function boot()
3942
Subuser::observe(SubuserObserver::class);
4043
EggVariable::observe(EggVariableObserver::class);
4144
}
45+
46+
public function shouldDiscoverEvents()
47+
{
48+
return true;
49+
}
4250
}

0 commit comments

Comments
 (0)