Skip to content

Commit

Permalink
#191: Reimplement Login Activity logging (#213)
Browse files Browse the repository at this point in the history
* Start replacing old Logins log

* Remove a couple of old panel widgets

* Register login activity and jobs

* Update model helpers

* Create/register Last Login widget

* Update weekly report email

* Add job tests

* Create login activity job, factory, and seeding

* Test pruning job

* Add login activity to dashboard

* Update CHANGELOG
  • Loading branch information
JSn1nj4 authored Aug 5, 2024
1 parent ad760f9 commit d1fb464
Show file tree
Hide file tree
Showing 28 changed files with 543 additions and 168 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
- [#202][is_202]: Condense migrations
- [#203][is_203]: Slim down cookie banner ([#210][pr_210])
- Minor housekeeping
- [#191][is_191]: Reimplement login activity ([#213][pr_213])

### Fixed

Expand All @@ -25,6 +26,8 @@

[is_184]: https://github.com/JSn1nj4/ElliotDerhay.com/issues/184

[is_191]: https://github.com/JSn1nj4/ElliotDerhay.com/issues/191

[is_198]: https://github.com/JSn1nj4/ElliotDerhay.com/issues/198

[is_200]: https://github.com/JSn1nj4/ElliotDerhay.com/issues/200
Expand Down Expand Up @@ -53,6 +56,8 @@

[pr_212]: https://github.com/JSn1nj4/ElliotDerhay.com/pull/212

[pr_213]: https://github.com/JSn1nj4/ElliotDerhay.com/pull/213

## Version 2.9.2

### Changes
Expand Down
14 changes: 0 additions & 14 deletions app/Actions/LogUserLogin.php

This file was deleted.

50 changes: 39 additions & 11 deletions app/Filament/Pages/Login.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,53 @@
namespace App\Filament\Pages;

use App\Features\AdminLogin;
use App\Jobs\LoginFailed;
use App\Jobs\LoginSucceeded;
use Filament\Http\Responses\Auth\Contracts\LoginResponse;
use Filament\Notifications\Notification;
use Filament\Pages\Auth\Login as BaseLoginPage;
use Illuminate\Support\Facades\Request;
use Laravel\Pennant\Feature;

class Login extends BaseLoginPage
{
public function authenticate(): LoginResponse|null
public function authenticate(): LoginResponse|null
{
if (Feature::inactive(AdminLogin::class)) {
Notification::make()
->title(__('Login Disabled'))
->body(__('Login is disabled right now. Please come back later.'))
->danger()
->send();

return null;
}
try {
if (Feature::inactive(AdminLogin::class)) {
Notification::make()
->title(__('Login Disabled'))
->body(__('Login is disabled right now. Please come back later.'))
->danger()
->send();

return null;
}

$response = parent::authenticate();

return parent::authenticate();
if ($response instanceof LoginResponse) {
LoginSucceeded::dispatch(
$this->getCredentialsFromFormData($this->form->getState())['email'],
Request::ip(),
);
} else {
LoginFailed::dispatch(
$this->getCredentialsFromFormData($this->form->getState())['email'],
'reason unknown',
Request::ip(),
);
}

return $response;
} catch (\Throwable $exception) {
LoginFailed::dispatch(
$this->getCredentialsFromFormData($this->form->getState())['email'],
$exception->getMessage(),
Request::ip(),
);

throw $exception;
}
}
}
76 changes: 76 additions & 0 deletions app/Filament/Resources/LoginActivityResource.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
<?php

namespace App\Filament\Resources;

use App\Filament\Resources\LoginActivityResource\Pages;
use App\Filament\Resources\LoginActivityResource\RelationManagers;
use App\Models\LoginActivity;
use Filament\Facades\Filament;
use Filament\Resources\Resource;
use Filament\Support\Colors\Color;
use Filament\Tables\Columns\IconColumn;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Filters\SelectFilter;
use Filament\Tables\Table;

class LoginActivityResource extends Resource
{
protected static string|null $model = LoginActivity::class;

protected static string|null $navigationGroup = 'Administration';

protected static string|null $navigationIcon = 'm-clipboard-document-list';

protected static string|null $navigationLabel = 'Login Activity';

protected static int|null $navigationSort = 2;

public static function table(Table $table): Table
{
return $table
->columns([
TextColumn::make('created_at')
->label('Time')
->dateTime()
->sortable(),

TextColumn::make('email')
->color(fn ($state) => match ($state) {
Filament::auth()->user()->email => 'white',
default => Color::Neutral,
})
->searchable(isIndividual: true, isGlobal: false),

IconColumn::make('succeeded')
->boolean(),

TextColumn::make('info')
->searchable(isIndividual: true, isGlobal: false),

TextColumn::make('ip_address')
->searchable(isIndividual: true, isGlobal: false)
->fontFamily('mono'),
])
->filters([
SelectFilter::make('succeeded')
->options([
true => 'Succeeded',
false => 'Failed',
]),
])
->actions([])
->bulkActions([]);
}

public static function getRelations(): array
{
return [];
}

public static function getPages(): array
{
return [
'index' => Pages\ListLoginActivities::route('/'),
];
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

namespace App\Filament\Resources\LoginActivityResource\Pages;

use App\Filament\Resources\LoginActivityResource;
use Filament\Resources\Pages\ListRecords;

class ListLoginActivities extends ListRecords
{
protected static string $resource = LoginActivityResource::class;

protected function getHeaderActions(): array
{
return [];
}
}
25 changes: 25 additions & 0 deletions app/Filament/Widgets/LastLoginWidget.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php

namespace App\Filament\Widgets;

use App\Models\LoginActivity;
use Carbon\Carbon;
use Filament\Widgets\Widget;

class LastLoginWidget extends Widget
{
protected int|string|array $columnSpan = 2;

protected static string $view = 'filament.widgets.last-login-widget';

public readonly Carbon $time;
public readonly string $ip_address;

public function mount()
{
$latest = LoginActivity::succeeded()->latest()->first();

$this->time = $latest->created_at;
$this->ip_address = $latest->ip_address;
}
}
33 changes: 33 additions & 0 deletions app/Jobs/LoginFailed.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

namespace App\Jobs;

use App\Models\LoginActivity;
use Illuminate\Foundation\Bus\Dispatchable;

class LoginFailed
{
use Dispatchable;

/**
* Create a new job instance.
*/
public function __construct(
#[\SensitiveParameter] public readonly string $email,
public readonly string|null $info,
#[\SensitiveParameter] public readonly string $ip_address,
) {}

/**
* Execute the job.
*/
public function handle(): void
{
LoginActivity::create([
'email' => $this->email,
'succeeded' => false,
'info' => $this->info,
'ip_address' => $this->ip_address,
]);
}
}
31 changes: 31 additions & 0 deletions app/Jobs/LoginSucceeded.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php

namespace App\Jobs;

use App\Models\LoginActivity;
use Illuminate\Foundation\Bus\Dispatchable;

class LoginSucceeded
{
use Dispatchable;

/**
* Create a new job instance.
*/
public function __construct(
#[\SensitiveParameter] public readonly string $email,
#[\SensitiveParameter] public readonly string $ip_address,
) {}

/**
* Execute the job.
*/
public function handle(): void
{
LoginActivity::create([
'email' => $this->email,
'succeeded' => true,
'ip_address' => $this->ip_address,
]);
}
}
26 changes: 26 additions & 0 deletions app/Jobs/PruneLoginActivityJob.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

namespace App\Jobs;

use App\Models\LoginActivity;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;

class PruneLoginActivityJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable;

public function __construct() {}

public function handle(): void
{
LoginActivity::whereDate(
'created_at',
'<=',
now()->startOfDay()
->subDays(config('auth.activity.days_to_keep'))
)->delete();
}
}
26 changes: 21 additions & 5 deletions app/Mail/WeeklyReportEmail.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace App\Mail;

use App\Models\LoginActivity;
use App\Models\Post;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
Expand All @@ -16,14 +17,16 @@ class WeeklyReportEmail extends Mailable
/**
* Create a new message instance.
*/
public function __construct() {
public function __construct()
{
//
}

/**
* Get the message envelope.
*/
public function envelope(): Envelope {
public function envelope(): Envelope
{
return new Envelope(
subject: 'Weekly Admin Report',
);
Expand All @@ -32,11 +35,23 @@ public function envelope(): Envelope {
/**
* Get the message content definition.
*/
public function content(): Content {
public function content(): Content
{
return new Content(
markdown: 'emails.reports.weekly',
with: [
'lastLogin' => '(needs implementing)',
'lastLogin' => LoginActivity::succeeded()
->latest()
->first()
?->created_at
->toDayDateTimeString() ?? 'No recent logins.',

'lastFailure' => LoginActivity::failed()
->latest()
->first()
?->created_at
->toDayDateTimeString() ?? 'No recent login failures.',

'postsPublished' => Post::publishedRecently()->count(),
],
);
Expand All @@ -47,7 +62,8 @@ public function content(): Content {
*
* @return array<int, \Illuminate\Mail\Mailables\Attachment>
*/
public function attachments(): array {
public function attachments(): array
{
return [];
}
}
3 changes: 3 additions & 0 deletions app/Models/CommandEvent.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@
* @method static \Illuminate\Database\Eloquent\Builder|CommandEvent whereMessage($value)
* @method static \Illuminate\Database\Eloquent\Builder|CommandEvent whereSucceeded($value)
* @method static \Illuminate\Database\Eloquent\Builder|CommandEvent whereUpdatedAt($value)
* @property-read mixed $date_at_time
* @property-read mixed $long_date_at_time
* @property-read mixed $short_date
* @mixin \Eloquent
*/
class CommandEvent extends Model
Expand Down
Loading

0 comments on commit d1fb464

Please sign in to comment.