From 5f28892153c0d02a50cc728e71ac3211c11c02f2 Mon Sep 17 00:00:00 2001
From: Roardom
Date: Sat, 17 Aug 2024 14:41:20 +0000
Subject: [PATCH] add: password reset history logging
---
app/Actions/Fortify/ResetUserPassword.php | 2 +
app/Actions/Fortify/UpdateUserPassword.php | 2 +
.../Staff/PasswordResetHistoryController.php | 30 ++++++++
.../Controllers/User/PasswordController.php | 28 +++++---
.../Livewire/PasswordResetHistorySearch.php | 69 +++++++++++++++++++
app/Models/PasswordResetHistory.php | 46 +++++++++++++
app/Models/User.php | 10 +++
...7_140412_create_password_reset_history.php | 35 ++++++++++
lang/en/user.php | 1 +
.../views/Staff/dashboard/index.blade.php | 9 +++
.../password-reset-history/index.blade.php | 32 +++++++++
.../password-reset-history-search.blade.php | 69 +++++++++++++++++++
resources/views/user/password/edit.blade.php | 33 +++++++++
routes/web.php | 7 ++
14 files changed, 363 insertions(+), 10 deletions(-)
create mode 100644 app/Http/Controllers/Staff/PasswordResetHistoryController.php
create mode 100644 app/Http/Livewire/PasswordResetHistorySearch.php
create mode 100644 app/Models/PasswordResetHistory.php
create mode 100644 database/migrations/2024_08_17_140412_create_password_reset_history.php
create mode 100644 resources/views/Staff/password-reset-history/index.blade.php
create mode 100644 resources/views/livewire/password-reset-history-search.blade.php
diff --git a/app/Actions/Fortify/ResetUserPassword.php b/app/Actions/Fortify/ResetUserPassword.php
index fc47985cdd..58308b9a9f 100644
--- a/app/Actions/Fortify/ResetUserPassword.php
+++ b/app/Actions/Fortify/ResetUserPassword.php
@@ -61,5 +61,7 @@ public function reset(User $user, array $input): void
$user->active = true;
$user->save();
+
+ $user->passwordResetHistories()->create();
}
}
diff --git a/app/Actions/Fortify/UpdateUserPassword.php b/app/Actions/Fortify/UpdateUserPassword.php
index d6eb7a24b9..e913a4209b 100644
--- a/app/Actions/Fortify/UpdateUserPassword.php
+++ b/app/Actions/Fortify/UpdateUserPassword.php
@@ -42,5 +42,7 @@ public function update(User $user, array $input): void
$user->forceFill([
'password' => Hash::make($input['password']),
])->save();
+
+ $user->passwordResetHistories()->create();
}
}
diff --git a/app/Http/Controllers/Staff/PasswordResetHistoryController.php b/app/Http/Controllers/Staff/PasswordResetHistoryController.php
new file mode 100644
index 0000000000..3022d15ec4
--- /dev/null
+++ b/app/Http/Controllers/Staff/PasswordResetHistoryController.php
@@ -0,0 +1,30 @@
+
+ * @license https://www.gnu.org/licenses/agpl-3.0.en.html/ GNU Affero General Public License v3.0
+ */
+
+namespace App\Http\Controllers\Staff;
+
+use App\Http\Controllers\Controller;
+
+class PasswordResetHistoryController extends Controller
+{
+ /**
+ * Display all user password reset histories.
+ */
+ public function index(): \Illuminate\Contracts\View\Factory|\Illuminate\View\View
+ {
+ return view('Staff.password-reset-history.index');
+ }
+}
diff --git a/app/Http/Controllers/User/PasswordController.php b/app/Http/Controllers/User/PasswordController.php
index 11c8359f4e..9af9544496 100644
--- a/app/Http/Controllers/User/PasswordController.php
+++ b/app/Http/Controllers/User/PasswordController.php
@@ -19,6 +19,7 @@
use App\Http\Controllers\Controller;
use App\Models\User;
use Illuminate\Http\Request;
+use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Hash;
use Illuminate\Validation\Rule;
use Illuminate\Validation\Rules\Password;
@@ -48,16 +49,20 @@ protected function update(Request $request, User $user): \Illuminate\Http\Redire
],
]);
- $user->update([
- 'password' => Hash::make($request->new_password),
- ]);
+ DB::transaction(function () use ($user, $request, $changedByStaff): void {
+ $user->update([
+ 'password' => Hash::make($request->new_password),
+ ]);
+
+ $user->passwordResetHistories()->create();
- if ($changedByStaff) {
- $user->sendSystemNotification(
- subject: 'ATTENTION - Your password has been changed',
- message: "Your password has been changed by staff. You will need to update your password manager with the new password.\n\nFor more information, please create a helpdesk ticket.",
- );
- }
+ if ($changedByStaff) {
+ $user->sendSystemNotification(
+ subject: 'ATTENTION - Your password has been changed',
+ message: "Your password has been changed by staff. You will need to update your password manager with the new password.\n\nFor more information, please create a helpdesk ticket.",
+ );
+ }
+ });
return to_route('users.password.edit', ['user' => $user])
->withSuccess('Your new password has been saved successfully.');
@@ -70,6 +75,9 @@ public function edit(Request $request, User $user): \Illuminate\Contracts\View\F
{
abort_unless($request->user()->is($user) || $request->user()->group->is_modo, 403);
- return view('user.password.edit', ['user' => $user]);
+ return view('user.password.edit', [
+ 'user' => $user,
+ 'passwordResetHistories' => $user->passwordResetHistories()->latest()->get(),
+ ]);
}
}
diff --git a/app/Http/Livewire/PasswordResetHistorySearch.php b/app/Http/Livewire/PasswordResetHistorySearch.php
new file mode 100644
index 0000000000..400883907a
--- /dev/null
+++ b/app/Http/Livewire/PasswordResetHistorySearch.php
@@ -0,0 +1,69 @@
+
+ * @license https://www.gnu.org/licenses/agpl-3.0.en.html/ GNU Affero General Public License v3.0
+ */
+
+namespace App\Http\Livewire;
+
+use App\Models\PasswordResetHistory;
+use App\Models\User;
+use App\Traits\LivewireSort;
+use Livewire\Attributes\Computed;
+use Livewire\Attributes\Url;
+use Livewire\Component;
+use Livewire\WithPagination;
+
+/**
+ * @property \Illuminate\Contracts\Pagination\LengthAwarePaginator $passwordResetHistories
+ */
+class PasswordResetHistorySearch extends Component
+{
+ use LivewireSort;
+ use WithPagination;
+
+ #TODO: Update URL attributes once Livewire 3 fixes upstream bug. See: https://github.com/livewire/livewire/discussions/7746
+
+ #[Url(history: true)]
+ public string $username = '';
+
+ #[Url(history: true)]
+ public string $sortField = 'created_at';
+
+ #[Url(history: true)]
+ public string $sortDirection = 'desc';
+
+ #[Url(history: true)]
+ public int $perPage = 25;
+
+ /**
+ * @return \Illuminate\Pagination\LengthAwarePaginator
+ */
+ #[Computed]
+ final public function passwordResetHistories(): \Illuminate\Pagination\LengthAwarePaginator
+ {
+ return PasswordResetHistory::with([
+ 'user' => fn ($query) => $query->withTrashed()->with('group'),
+ ])
+ ->when($this->username, fn ($query) => $query->whereIn('user_id', User::withTrashed()->select('id')->where('username', 'LIKE', '%'.$this->username.'%')))
+ ->orderBy($this->sortField, $this->sortDirection)
+ ->paginate($this->perPage);
+ }
+
+ final public function render(): \Illuminate\Contracts\View\View|\Illuminate\Foundation\Application|\Illuminate\Contracts\View\Factory|\Illuminate\Contracts\Foundation\Application
+ {
+ return view('livewire.password-reset-history-search', [
+ 'passwordResetHistories' => $this->passwordResetHistories,
+ ]);
+ }
+}
diff --git a/app/Models/PasswordResetHistory.php b/app/Models/PasswordResetHistory.php
new file mode 100644
index 0000000000..f13e1aa7ac
--- /dev/null
+++ b/app/Models/PasswordResetHistory.php
@@ -0,0 +1,46 @@
+ 'datetime',
+ ];
+ }
+
+ /**
+ * Has Many Torrents.
+ *
+ * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
+ */
+ public function user(): \Illuminate\Database\Eloquent\Relations\BelongsTo
+ {
+ return $this->belongsTo(User::class);
+ }
+}
diff --git a/app/Models/User.php b/app/Models/User.php
index 0604835332..e2abf44130 100644
--- a/app/Models/User.php
+++ b/app/Models/User.php
@@ -924,6 +924,16 @@ public function emailUpdates(): \Illuminate\Database\Eloquent\Relations\HasMany
return $this->hasMany(EmailUpdate::class);
}
+ /**
+ * Has many password reset histories.
+ *
+ * @return \Illuminate\Database\Eloquent\Relations\HasMany
+ */
+ public function passwordResetHistories(): \Illuminate\Database\Eloquent\Relations\HasMany
+ {
+ return $this->hasMany(PasswordResetHistory::class);
+ }
+
/**
* Has many torrent trumps.
*
diff --git a/database/migrations/2024_08_17_140412_create_password_reset_history.php b/database/migrations/2024_08_17_140412_create_password_reset_history.php
new file mode 100644
index 0000000000..5b94fe08b6
--- /dev/null
+++ b/database/migrations/2024_08_17_140412_create_password_reset_history.php
@@ -0,0 +1,35 @@
+
+ * @license https://www.gnu.org/licenses/agpl-3.0.en.html/ GNU Affero General Public License v3.0
+ */
+
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+
+return new class () extends Migration {
+ /**
+ * Run the migrations.
+ */
+ public function up(): void
+ {
+ Schema::create('password_reset_histories', function (Blueprint $table): void {
+ $table->increments('id');
+ $table->unsignedInteger('user_id');
+ $table->timestamp('created_at')->nullable()->useCurrent();
+
+ $table->foreign('user_id')->references('id')->on('users')->cascadeOnUpdate();
+ });
+ }
+};
diff --git a/lang/en/user.php b/lang/en/user.php
index 7a7391b5cf..8939f45c0f 100644
--- a/lang/en/user.php
+++ b/lang/en/user.php
@@ -245,6 +245,7 @@
'other-privacy-online' => 'Allow users to see you in the online users block',
'passkey' => 'Passkey',
'passkey-warning' => 'PID is like your password, you must keep it safe!',
+ 'password-resets' => 'Password Resets',
'pending-achievements' => 'Pending Achievements',
'per-torrent' => 'Per Torrent',
'posts' => 'Posts',
diff --git a/resources/views/Staff/dashboard/index.blade.php b/resources/views/Staff/dashboard/index.blade.php
index f3af711540..88545245df 100644
--- a/resources/views/Staff/dashboard/index.blade.php
+++ b/resources/views/Staff/dashboard/index.blade.php
@@ -432,6 +432,15 @@ class="form__button form__button--text"
{{ __('user.email-updates') }}
+
+
+
+ {{ __('user.password-resets') }}
+
+
+ {{ __('common.user') }} {{ __('user.password-resets') }} -
+ {{ __('staff.staff-dashboard') }} - {{ config('other.title') }}
+
+@endsection
+
+@section('meta')
+
+@endsection
+
+@section('breadcrumbs')
+
+
+ {{ __('staff.staff-dashboard') }}
+
+
+
+ {{ __('user.password-resets') }}
+
+@endsection
+
+@section('page', 'page__password-reset-history-log--index')
+
+@section('main')
+ @livewire('password-reset-history-search')
+@endsection
diff --git a/resources/views/livewire/password-reset-history-search.blade.php b/resources/views/livewire/password-reset-history-search.blade.php
new file mode 100644
index 0000000000..7e0093f06b
--- /dev/null
+++ b/resources/views/livewire/password-reset-history-search.blade.php
@@ -0,0 +1,69 @@
+
+
+
+
+
+
+
+ {{ __('common.username') }}
+ @include('livewire.includes._sort-icon', ['field' => 'user_id'])
+ |
+
+ {{ __('common.created_at') }}
+ @include('livewire.includes._sort-icon', ['field' => 'created_at'])
+ |
+
+ @forelse ($passwordResetHistories as $passwordResetHistory)
+
+
+
+ |
+
+
+ |
+
+ @empty
+
+ No Password Resets |
+
+ @endforelse
+
+
+
+ {{ $passwordResetHistories->links('partials.pagination') }}
+
diff --git a/resources/views/user/password/edit.blade.php b/resources/views/user/password/edit.blade.php
index 22949ba9ed..fe1e6383bf 100644
--- a/resources/views/user/password/edit.blade.php
+++ b/resources/views/user/password/edit.blade.php
@@ -73,3 +73,36 @@ class="form__text"
@endsection
+
+@section('sidebar')
+
+ {{ __('user.password-resets') }}
+
+
+
+
+ {{ __('common.created_at') }} |
+
+
+
+ @forelse ($passwordResetHistories as $passwordResetHistory)
+
+
+
+ |
+
+ @empty
+
+ No password reset history |
+
+ @endforelse
+
+
+
+
+@endsection
diff --git a/routes/web.php b/routes/web.php
index f45327cb00..27d144fa50 100644
--- a/routes/web.php
+++ b/routes/web.php
@@ -974,6 +974,13 @@
});
});
+ // Password Reset Histories
+ Route::prefix('password-reset-histories')->group(function (): void {
+ Route::name('password_reset_histories.')->group(function (): void {
+ Route::get('/', [App\Http\Controllers\Staff\PasswordResetHistoryController::class, 'index'])->name('index');
+ });
+ });
+
// Peers
Route::prefix('peers')->group(function (): void {
Route::name('peers.')->group(function (): void {