From c94dd25123f034b4ac95cddd8a1322bbe983ff8c Mon Sep 17 00:00:00 2001 From: Jesse Schutt Date: Tue, 7 May 2024 10:47:40 -0500 Subject: [PATCH] Adds customizable email address --- README.md | 13 ++++++- .../migrations/create_users_table.php.stub | 3 +- src/Traits/Securable.php | 7 +++- tests/Setup/Factories/UserFactory.php | 3 +- tests/Setup/Models/CustomUser.php | 22 ++++++++++++ tests/Unit/Events/SecureFieldsUpdatedTest.php | 36 ++++++++++++++++++- 6 files changed, 79 insertions(+), 5 deletions(-) create mode 100644 tests/Setup/Models/CustomUser.php diff --git a/README.md b/README.md index 2a0f771..9762eee 100644 --- a/README.md +++ b/README.md @@ -128,6 +128,17 @@ If you would like to further customize the notifications, you can configure the ], ``` +#### Customizing Email Address + +By default, this package assumes that the user model has an `email` attribute. If you would like to customize the email address that notifications are sent to, you can override the `sendSecurityEmailsTo` method on the models that utilize the `Securable` trait. + +```php +public function sendSecurityEmailsTo(): string +{ + return $this->getOriginal('alternate_email') ?? $this->alternate_email; +} +```` + ### Custom IP Address Driver If you would like to have full control over IP address and login handling, you can create a custom driver by implementing the `Zaengle\LaravelSecurityNotifications\Services\DigestIPAddress` interface like the example below. @@ -174,4 +185,4 @@ The MIT License (MIT). Please see [License File](LICENSE.md) for more informatio ## Credits -- [Header Image](https://unsplash.com/photos/a-blurry-photo-of-lights-in-the-dark-AHBNGvRTm_A) \ No newline at end of file +- [Header Image](https://unsplash.com/photos/a-blurry-photo-of-lights-in-the-dark-AHBNGvRTm_A) diff --git a/database/migrations/create_users_table.php.stub b/database/migrations/create_users_table.php.stub index 5a1875a..d3090d7 100644 --- a/database/migrations/create_users_table.php.stub +++ b/database/migrations/create_users_table.php.stub @@ -14,6 +14,7 @@ class CreateUsersTable extends Migration $table->string('username')->unique(); $table->string('email')->unique(); $table->timestamp('email_verified_at')->nullable(); + $table->string('alternate_email')->unique(); $table->string('password'); $table->rememberToken(); $table->timestamps(); @@ -24,4 +25,4 @@ class CreateUsersTable extends Migration { Schema::dropIfExists('users'); } -} \ No newline at end of file +} diff --git a/src/Traits/Securable.php b/src/Traits/Securable.php index 00c6146..55ec56f 100644 --- a/src/Traits/Securable.php +++ b/src/Traits/Securable.php @@ -25,7 +25,7 @@ public static function bootSecurable(): void event(new SecureFieldsUpdated( $model, $changedSecureFields->toArray(), - $model->getOriginal('email') ?? $model->email, + $model->sendSecurityEmailsTo(), $model->refresh()->updated_at, )); } @@ -33,6 +33,11 @@ public static function bootSecurable(): void }); } + public function sendSecurityEmailsTo(): string + { + return $this->getOriginal('email') ?? $this->email; + } + public function getSecureFields(): array { return $this->secureFields; diff --git a/tests/Setup/Factories/UserFactory.php b/tests/Setup/Factories/UserFactory.php index c283631..22fb17a 100644 --- a/tests/Setup/Factories/UserFactory.php +++ b/tests/Setup/Factories/UserFactory.php @@ -16,9 +16,10 @@ public function definition(): array 'name' => $this->faker->name, 'username' => $this->faker->unique()->userName, 'email' => $this->faker->unique()->safeEmail, + 'alternate_email' => $this->faker->unique()->safeEmail, 'email_verified_at' => now(), 'password' => bcrypt('password'), 'remember_token' => Str::random(10), ]; } -} \ No newline at end of file +} diff --git a/tests/Setup/Models/CustomUser.php b/tests/Setup/Models/CustomUser.php new file mode 100644 index 0000000..d831553 --- /dev/null +++ b/tests/Setup/Models/CustomUser.php @@ -0,0 +1,22 @@ +getOriginal('alternate_email') ?? $this->alternate_email; + } +} diff --git a/tests/Unit/Events/SecureFieldsUpdatedTest.php b/tests/Unit/Events/SecureFieldsUpdatedTest.php index 4a11bfe..2c7c371 100644 --- a/tests/Unit/Events/SecureFieldsUpdatedTest.php +++ b/tests/Unit/Events/SecureFieldsUpdatedTest.php @@ -4,6 +4,7 @@ use Illuminate\Support\Facades\Config; use Illuminate\Support\Facades\Event; use Zaengle\LaravelSecurityNotifications\Events\SecureFieldsUpdated; +use Zaengle\LaravelSecurityNotifications\Tests\Setup\Models\CustomUser; use Zaengle\LaravelSecurityNotifications\Tests\Setup\Models\User; it('emits event when secure fields are updated', function () { @@ -13,6 +14,8 @@ $user = User::factory()->create(); + $originalEmail = $user->email; + $user->update([ 'name' => 'New Name', // Not a secure field 'email' => 'new@email.com', @@ -20,8 +23,39 @@ 'password' => bcrypt('newpassword'), ]); - Event::assertDispatched(SecureFieldsUpdated::class, function ($event) use ($user) { + Event::assertDispatched(SecureFieldsUpdated::class, function (SecureFieldsUpdated $event) use ($user, $originalEmail) { return $event->model->is($user) + && $event->original_email === $originalEmail + && Arr::has($event->fields, 'email') + && Arr::has($event->fields, 'username') + && Arr::has($event->fields, 'password') + && ! Arr::has($event->fields, 'name'); + }); +}); + +it('emits event when secure fields are updated and sends notification to configured email', function () { + Event::fake([ + SecureFieldsUpdated::class, + ]); + + $customUser = new CustomUser(); + $customUser->setRawAttributes( + User::factory()->make([ + 'alternate_email' => 'alternate_email@example.com', + ])->getAttributes() + ); + $customUser->save(); + + $customUser->update([ + 'name' => 'New Name', // Not a secure field + 'email' => 'new@email.com', + 'username' => 'newusername', + 'password' => bcrypt('newpassword'), + ]); + + Event::assertDispatched(SecureFieldsUpdated::class, function (SecureFieldsUpdated $event) use ($customUser) { + return $event->model->is($customUser) + && $event->original_email === 'alternate_email@example.com' && Arr::has($event->fields, 'email') && Arr::has($event->fields, 'username') && Arr::has($event->fields, 'password')