mirror of
https://github.com/firefly-iii/firefly-iii.git
synced 2026-04-29 11:03:00 +00:00
Refactor events for #11544
This commit is contained in:
@@ -1,8 +1,7 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* DetectedNewIPAddress.php
|
||||
* Copyright (c) 2021 james@firefly-iii.org
|
||||
* UserHasNewIpAddress.php
|
||||
* Copyright (c) 2026 james@firefly-iii.org
|
||||
*
|
||||
* This file is part of Firefly III (https://github.com/firefly-iii).
|
||||
*
|
||||
@@ -20,17 +19,13 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Events;
|
||||
namespace FireflyIII\Events\Security\User;
|
||||
|
||||
use FireflyIII\Events\Event;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
/**
|
||||
* Class DetectedNewIPAddress
|
||||
*/
|
||||
class DetectedNewIPAddress extends Event
|
||||
class UserLoggedInFromNewIpAddress extends Event
|
||||
{
|
||||
use SerializesModels;
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* ActuallyLoggedIn.php
|
||||
* Copyright (c) 2022 james@firefly-iii.org
|
||||
* UserSuccessfullyLoggedIn.php
|
||||
* Copyright (c) 2026 james@firefly-iii.org
|
||||
*
|
||||
* This file is part of Firefly III (https://github.com/firefly-iii).
|
||||
*
|
||||
@@ -20,18 +19,15 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Events;
|
||||
namespace FireflyIII\Events\Security\User;
|
||||
|
||||
use FireflyIII\Events\Event;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Contracts\Auth\Authenticatable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* Class ActuallyLoggedIn
|
||||
*/
|
||||
class ActuallyLoggedIn extends Event
|
||||
class UserSuccessfullyLoggedIn extends Event
|
||||
{
|
||||
use SerializesModels;
|
||||
|
||||
@@ -42,5 +38,6 @@ class ActuallyLoggedIn extends Event
|
||||
if ($user instanceof User) {
|
||||
$this->user = $user;
|
||||
}
|
||||
throw new InvalidArgumentException('User must be an instance of User.');
|
||||
}
|
||||
}
|
||||
@@ -28,7 +28,7 @@ use FireflyIII\Notifications\Notifiables\OwnerNotifiable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class OwnerTestNotificationChannel
|
||||
class OwnerTestsNotificationChannel
|
||||
{
|
||||
use SerializesModels;
|
||||
|
||||
@@ -39,7 +39,7 @@ class OwnerTestNotificationChannel
|
||||
*/
|
||||
public function __construct(string $channel, public OwnerNotifiable $owner)
|
||||
{
|
||||
Log::debug(sprintf('Triggered OwnerTestNotificationChannel("%s")', $channel));
|
||||
Log::debug(sprintf('Triggered OwnerTestsNotificationChannels("%s")', $channel));
|
||||
$this->channel = $channel;
|
||||
}
|
||||
}
|
||||
@@ -28,7 +28,7 @@ use FireflyIII\User;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class UserTestNotificationChannel
|
||||
class UserTestsNotificationChannel
|
||||
{
|
||||
use SerializesModels;
|
||||
|
||||
@@ -26,7 +26,7 @@ namespace FireflyIII\Handlers\Events;
|
||||
use Exception;
|
||||
use FireflyIII\Events\Admin\InvitationCreated;
|
||||
use FireflyIII\Events\NewVersionAvailable;
|
||||
use FireflyIII\Events\Test\OwnerTestNotificationChannel;
|
||||
use FireflyIII\Events\Test\OwnerTestsNotificationChannel;
|
||||
use FireflyIII\Notifications\Admin\UserInvitation;
|
||||
use FireflyIII\Notifications\Admin\VersionCheckResult;
|
||||
use FireflyIII\Notifications\Notifiables\OwnerNotifiable;
|
||||
@@ -97,59 +97,4 @@ class AdminEventHandler
|
||||
Log::error($e->getTraceAsString());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a test message to an administrator.
|
||||
*/
|
||||
public function sendTestNotification(OwnerTestNotificationChannel $event): void
|
||||
{
|
||||
Log::debug(sprintf('Now in sendTestNotification("%s")', $event->channel));
|
||||
|
||||
switch ($event->channel) {
|
||||
case 'email':
|
||||
$class = OwnerTestNotificationEmail::class;
|
||||
|
||||
break;
|
||||
|
||||
case 'slack':
|
||||
$class = OwnerTestNotificationSlack::class;
|
||||
|
||||
break;
|
||||
|
||||
// case 'ntfy':
|
||||
// $class = OwnerTestNotificationNtfy::class;
|
||||
//
|
||||
// break;
|
||||
|
||||
case 'pushover':
|
||||
$class = OwnerTestNotificationPushover::class;
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
Log::error(sprintf('Unknown channel "%s" in sendTestNotification method.', $event->channel));
|
||||
|
||||
return;
|
||||
}
|
||||
Log::debug(sprintf('Will send %s as a notification.', $class));
|
||||
|
||||
try {
|
||||
Notification::send($event->owner, new $class());
|
||||
} catch (Exception $e) {
|
||||
$message = $e->getMessage();
|
||||
if (str_contains($message, 'Bcc')) {
|
||||
Log::warning('[Bcc] Could not send notification. Please validate your email settings, use the .env.example file as a guide.');
|
||||
|
||||
return;
|
||||
}
|
||||
if (str_contains($message, 'RFC 2822')) {
|
||||
Log::warning('[RFC] Could not send notification. Please validate your email settings, use the .env.example file as a guide.');
|
||||
|
||||
return;
|
||||
}
|
||||
Log::error($e->getMessage());
|
||||
Log::error($e->getTraceAsString());
|
||||
}
|
||||
Log::debug(sprintf('If you see no errors above this line, test notification was sent over channel "%s"', $event->channel));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,16 +23,12 @@ declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Handlers\Events;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Database\Seeders\ExchangeRateSeeder;
|
||||
use Exception;
|
||||
use FireflyIII\Enums\UserRoleEnum;
|
||||
use FireflyIII\Events\ActuallyLoggedIn;
|
||||
use FireflyIII\Events\Admin\InvitationCreated;
|
||||
use FireflyIII\Events\DetectedNewIPAddress;
|
||||
use FireflyIII\Events\RegisteredUser;
|
||||
use FireflyIII\Events\RequestedNewPassword;
|
||||
use FireflyIII\Events\Test\UserTestNotificationChannel;
|
||||
use FireflyIII\Events\UserChangedEmail;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Mail\ConfirmEmailChangeMail;
|
||||
@@ -42,10 +38,6 @@ use FireflyIII\Models\GroupMembership;
|
||||
use FireflyIII\Models\UserGroup;
|
||||
use FireflyIII\Models\UserRole;
|
||||
use FireflyIII\Notifications\Admin\UserRegistration as AdminRegistrationNotification;
|
||||
use FireflyIII\Notifications\Test\UserTestNotificationEmail;
|
||||
use FireflyIII\Notifications\Test\UserTestNotificationPushover;
|
||||
use FireflyIII\Notifications\Test\UserTestNotificationSlack;
|
||||
use FireflyIII\Notifications\User\UserLogin;
|
||||
use FireflyIII\Notifications\User\UserNewPassword;
|
||||
use FireflyIII\Notifications\User\UserRegistration as UserRegistrationNotification;
|
||||
use FireflyIII\Repositories\User\UserRepositoryInterface;
|
||||
@@ -90,8 +82,8 @@ class UserEventHandler
|
||||
$repository = app(UserRepositoryInterface::class);
|
||||
|
||||
/** @var User $user */
|
||||
$user = $event->user;
|
||||
$count = $repository->count();
|
||||
$user = $event->user;
|
||||
$count = $repository->count();
|
||||
|
||||
// only act when there is 1 user in the system and he has no admin rights.
|
||||
if (1 === $count && !$repository->hasRole($user, 'owner')) {
|
||||
@@ -123,13 +115,13 @@ class UserEventHandler
|
||||
*/
|
||||
public function createGroupMembership(RegisteredUser $event): void
|
||||
{
|
||||
$user = $event->user;
|
||||
$groupExists = true;
|
||||
$groupTitle = $user->email;
|
||||
$index = 1;
|
||||
$user = $event->user;
|
||||
$groupExists = true;
|
||||
$groupTitle = $user->email;
|
||||
$index = 1;
|
||||
|
||||
/** @var null|UserGroup $group */
|
||||
$group = null;
|
||||
$group = null;
|
||||
|
||||
// create a new group.
|
||||
while ($groupExists) { // @phpstan-ignore-line
|
||||
@@ -139,7 +131,7 @@ class UserEventHandler
|
||||
|
||||
break;
|
||||
}
|
||||
$groupTitle = sprintf('%s-%d', $user->email, $index);
|
||||
$groupTitle = sprintf('%s-%d', $user->email, $index);
|
||||
++$index;
|
||||
if ($index > 99) {
|
||||
throw new FireflyException('Email address can no longer be used for registrations.');
|
||||
@@ -147,7 +139,7 @@ class UserEventHandler
|
||||
}
|
||||
|
||||
/** @var null|UserRole $role */
|
||||
$role = UserRole::where('title', UserRoleEnum::OWNER->value)->first();
|
||||
$role = UserRole::where('title', UserRoleEnum::OWNER->value)->first();
|
||||
if (null === $role) {
|
||||
throw new FireflyException('The user role is unexpectedly empty. Did you run all migrations?');
|
||||
}
|
||||
@@ -171,7 +163,7 @@ class UserEventHandler
|
||||
$repository = app(UserRepositoryInterface::class);
|
||||
|
||||
/** @var User $user */
|
||||
$user = $event->user;
|
||||
$user = $event->user;
|
||||
if ($repository->hasRole($user, 'demo')) {
|
||||
// set user back to English.
|
||||
Preferences::setForUser($user, 'language', 'en_US');
|
||||
@@ -181,46 +173,6 @@ class UserEventHandler
|
||||
}
|
||||
}
|
||||
|
||||
public function notifyNewIPAddress(DetectedNewIPAddress $event): void
|
||||
{
|
||||
$user = $event->user;
|
||||
|
||||
if ($user->hasRole('demo')) {
|
||||
return; // do not email demo user.
|
||||
}
|
||||
|
||||
$list = Preferences::getForUser($user, 'login_ip_history', [])->data;
|
||||
if (!is_array($list)) {
|
||||
$list = [];
|
||||
}
|
||||
|
||||
/** @var array $entry */
|
||||
foreach ($list as $index => $entry) {
|
||||
if (false === $entry['notified']) {
|
||||
try {
|
||||
Notification::send($user, new UserLogin());
|
||||
} catch (Exception $e) {
|
||||
$message = $e->getMessage();
|
||||
if (str_contains($message, 'Bcc')) {
|
||||
Log::warning('[Bcc] Could not send notification. Please validate your email settings, use the .env.example file as a guide.');
|
||||
|
||||
return;
|
||||
}
|
||||
if (str_contains($message, 'RFC 2822')) {
|
||||
Log::warning('[RFC] Could not send notification. Please validate your email settings, use the .env.example file as a guide.');
|
||||
|
||||
return;
|
||||
}
|
||||
Log::error($e->getMessage());
|
||||
Log::error($e->getTraceAsString());
|
||||
}
|
||||
}
|
||||
$list[$index]['notified'] = true;
|
||||
}
|
||||
|
||||
Preferences::setForUser($user, 'login_ip_history', $list);
|
||||
}
|
||||
|
||||
public function sendAdminRegistrationNotification(RegisteredUser $event): void
|
||||
{
|
||||
$sendMail = (bool)FireflyConfig::get('notification_admin_new_reg', true)->data;
|
||||
@@ -366,116 +318,4 @@ class UserEventHandler
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a test message to an administrator.
|
||||
*/
|
||||
public function sendTestNotification(UserTestNotificationChannel $event): void
|
||||
{
|
||||
Log::debug(sprintf('Now in (user) sendTestNotification("%s")', $event->channel));
|
||||
|
||||
switch ($event->channel) {
|
||||
case 'email':
|
||||
$class = UserTestNotificationEmail::class;
|
||||
|
||||
break;
|
||||
|
||||
case 'slack':
|
||||
$class = UserTestNotificationSlack::class;
|
||||
|
||||
break;
|
||||
|
||||
// case 'ntfy':
|
||||
// $class = UserTestNotificationNtfy::class;
|
||||
//
|
||||
// break;
|
||||
|
||||
case 'pushover':
|
||||
$class = UserTestNotificationPushover::class;
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
Log::error(sprintf('Unknown channel "%s" in (user) sendTestNotification method.', $event->channel));
|
||||
|
||||
return;
|
||||
}
|
||||
Log::debug(sprintf('Will send %s as a notification.', $class));
|
||||
|
||||
try {
|
||||
Notification::send($event->user, new $class());
|
||||
} catch (Exception $e) {
|
||||
$message = $e->getMessage();
|
||||
if (str_contains($message, 'Bcc')) {
|
||||
Log::warning('[Bcc] Could not send notification. Please validate your email settings, use the .env.example file as a guide.');
|
||||
|
||||
return;
|
||||
}
|
||||
if (str_contains($message, 'RFC 2822')) {
|
||||
Log::warning('[RFC] Could not send notification. Please validate your email settings, use the .env.example file as a guide.');
|
||||
|
||||
return;
|
||||
}
|
||||
Log::error($e->getMessage());
|
||||
Log::error($e->getTraceAsString());
|
||||
}
|
||||
Log::debug(sprintf('If you see no errors above this line, test notification was sent over channel "%s"', $event->channel));
|
||||
}
|
||||
|
||||
public function storeUserIPAddress(ActuallyLoggedIn $event): void
|
||||
{
|
||||
Log::debug('Now in storeUserIPAddress');
|
||||
$user = $event->user;
|
||||
|
||||
if ($user->hasRole('demo')) {
|
||||
Log::debug('Do not log demo user logins');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
/** @var array $preference */
|
||||
$preference = Preferences::getForUser($user, 'login_ip_history', [])->data;
|
||||
} catch (FireflyException $e) {
|
||||
// don't care.
|
||||
Log::error($e->getMessage());
|
||||
|
||||
return;
|
||||
}
|
||||
$inArray = false;
|
||||
$ip = request()->ip();
|
||||
Log::debug(sprintf('User logging in from IP address %s', $ip));
|
||||
|
||||
// update array if in array
|
||||
foreach ($preference as $index => $row) {
|
||||
if ($row['ip'] === $ip) {
|
||||
Log::debug('Found IP in array, refresh time.');
|
||||
$preference[$index]['time'] = now(config('app.timezone'))->format('Y-m-d H:i:s');
|
||||
$inArray = true;
|
||||
}
|
||||
// clean up old entries (6 months)
|
||||
$carbon = Carbon::createFromFormat('Y-m-d H:i:s', $preference[$index]['time']);
|
||||
if ($carbon instanceof Carbon && $carbon->diffInMonths(today(), true) > 6) {
|
||||
Log::debug(sprintf('Entry for %s is very old, remove it.', $row['ip']));
|
||||
unset($preference[$index]);
|
||||
}
|
||||
}
|
||||
// add to array if not the case:
|
||||
if (false === $inArray) {
|
||||
$preference[] = [
|
||||
'ip' => $ip,
|
||||
'time' => now(config('app.timezone'))->format('Y-m-d H:i:s'),
|
||||
'notified' => false,
|
||||
];
|
||||
}
|
||||
$preference = array_values($preference);
|
||||
|
||||
/** @var bool $send */
|
||||
$send = Preferences::getForUser($user, 'notification_user_login', true)->data;
|
||||
Preferences::setForUser($user, 'login_ip_history', $preference);
|
||||
|
||||
if (false === $inArray && true === $send) {
|
||||
event(new DetectedNewIPAddress($user));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Http\Controllers\Admin;
|
||||
|
||||
use FireflyIII\Events\Test\OwnerTestNotificationChannel;
|
||||
use FireflyIII\Events\Test\OwnerTestsNotificationChannel;
|
||||
use FireflyIII\Http\Controllers\Controller;
|
||||
use FireflyIII\Http\Requests\NotificationRequest;
|
||||
use FireflyIII\Notifications\Notifiables\OwnerNotifiable;
|
||||
@@ -127,7 +127,7 @@ class NotificationController extends Controller
|
||||
case 'ntfy':
|
||||
$owner = new OwnerNotifiable();
|
||||
Log::debug(sprintf('Now in testNotification("%s") controller.', $channel));
|
||||
event(new OwnerTestNotificationChannel($channel, $owner));
|
||||
event(new OwnerTestsNotificationChannel($channel, $owner));
|
||||
session()->flash('success', (string) trans('firefly.notification_test_executed', ['channel' => $channel]));
|
||||
}
|
||||
|
||||
|
||||
@@ -24,9 +24,9 @@ declare(strict_types=1);
|
||||
namespace FireflyIII\Http\Controllers\Auth;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use FireflyIII\Events\ActuallyLoggedIn;
|
||||
use FireflyIII\Events\Security\System\UnknownUserTriedLogin;
|
||||
use FireflyIII\Events\Security\User\UserFailedLoginAttempt;
|
||||
use FireflyIII\Events\Security\User\UserSuccessfullyLoggedIn;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Http\Controllers\Controller;
|
||||
use FireflyIII\Providers\RouteServiceProvider;
|
||||
@@ -70,7 +70,7 @@ class LoginController extends Controller
|
||||
protected string $redirectTo = RouteServiceProvider::HOME;
|
||||
private UserRepositoryInterface $repository;
|
||||
|
||||
private string $username = 'email';
|
||||
private string $username = 'email';
|
||||
|
||||
/**
|
||||
* Create a new controller instance.
|
||||
@@ -87,7 +87,7 @@ class LoginController extends Controller
|
||||
*
|
||||
* @throws ValidationException
|
||||
*/
|
||||
public function login(Request $request): JsonResponse|RedirectResponse
|
||||
public function login(Request $request): JsonResponse | RedirectResponse
|
||||
{
|
||||
$username = $request->get($this->username());
|
||||
Log::channel('audit')->info(sprintf('User is trying to login using "%s"', $username));
|
||||
@@ -105,8 +105,7 @@ class LoginController extends Controller
|
||||
$this->username => trans('auth.failed'),
|
||||
]
|
||||
)
|
||||
->onlyInput($this->username)
|
||||
;
|
||||
->onlyInput($this->username);
|
||||
}
|
||||
Log::debug('Login data is present.');
|
||||
|
||||
@@ -129,7 +128,7 @@ class LoginController extends Controller
|
||||
|
||||
// send a custom login event because laravel will also fire a login event if a "remember me"-cookie
|
||||
// restores the event.
|
||||
event(new ActuallyLoggedIn($this->guard()->user()));
|
||||
event(new UserSuccessfullyLoggedIn($this->guard()->user()));
|
||||
|
||||
return $this->sendLoginResponse($request);
|
||||
}
|
||||
@@ -187,10 +186,10 @@ class LoginController extends Controller
|
||||
/**
|
||||
* Log the user out of the application.
|
||||
*/
|
||||
public function logout(Request $request): Redirector|RedirectResponse|Response
|
||||
public function logout(Request $request): Redirector | RedirectResponse | Response
|
||||
{
|
||||
$authGuard = config('firefly.authentication_guard');
|
||||
$logoutUrl = config('firefly.custom_logout_url');
|
||||
$authGuard = config('firefly.authentication_guard');
|
||||
$logoutUrl = config('firefly.custom_logout_url');
|
||||
if ('remote_user_guard' === $authGuard && '' !== $logoutUrl) {
|
||||
return redirect($logoutUrl);
|
||||
}
|
||||
@@ -224,13 +223,13 @@ class LoginController extends Controller
|
||||
* @throws ContainerExceptionInterface
|
||||
* @throws NotFoundExceptionInterface
|
||||
*/
|
||||
public function showLoginForm(Request $request): Factory|Redirector|RedirectResponse|View
|
||||
public function showLoginForm(Request $request): Factory | Redirector | RedirectResponse | View
|
||||
{
|
||||
Log::channel('audit')->info('Show login form (1.1).');
|
||||
|
||||
$count = DB::table('users')->count();
|
||||
$guard = config('auth.defaults.guard');
|
||||
$title = (string)trans('firefly.login_page_title');
|
||||
$count = DB::table('users')->count();
|
||||
$guard = config('auth.defaults.guard');
|
||||
$title = (string)trans('firefly.login_page_title');
|
||||
|
||||
if (0 === $count && 'web' === $guard) {
|
||||
return redirect(route('register'));
|
||||
@@ -250,15 +249,15 @@ class LoginController extends Controller
|
||||
$allowReset = false;
|
||||
}
|
||||
|
||||
$email = $request->old('email');
|
||||
$remember = $request->old('remember');
|
||||
$email = $request->old('email');
|
||||
$remember = $request->old('remember');
|
||||
|
||||
$storeInCookie = config('google2fa.store_in_cookie', false);
|
||||
$storeInCookie = config('google2fa.store_in_cookie', false);
|
||||
if (false !== $storeInCookie) {
|
||||
$cookieName = config('google2fa.cookie_name', 'google2fa_token');
|
||||
Cookie::queue(Cookie::make($cookieName, 'invalid-'.Carbon::now()->getTimestamp()));
|
||||
Cookie::queue(Cookie::make($cookieName, 'invalid-' . Carbon::now()->getTimestamp()));
|
||||
}
|
||||
$usernameField = $this->username();
|
||||
$usernameField = $this->username();
|
||||
|
||||
return view('auth.login', ['allowRegistration' => $allowRegistration, 'email' => $email, 'remember' => $remember, 'allowReset' => $allowReset, 'title' => $title, 'usernameField' => $usernameField]);
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ namespace FireflyIII\Http\Controllers;
|
||||
use Carbon\Carbon;
|
||||
use FireflyIII\Enums\AccountTypeEnum;
|
||||
use FireflyIII\Events\Preferences\UserGroupChangedPrimaryCurrency;
|
||||
use FireflyIII\Events\Test\UserTestNotificationChannel;
|
||||
use FireflyIII\Events\Test\UserTestsNotificationChannel;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Http\Requests\PreferencesRequest;
|
||||
use FireflyIII\Models\Account;
|
||||
@@ -348,7 +348,7 @@ class PreferencesController extends Controller
|
||||
/** @var User $user */
|
||||
$user = auth()->user();
|
||||
Log::debug(sprintf('Now in testNotification("%s") controller.', $channel));
|
||||
event(new UserTestNotificationChannel($channel, $user));
|
||||
event(new UserTestsNotificationChannel($channel, $user));
|
||||
session()->flash('success', (string)trans('firefly.notification_test_executed', ['channel' => $channel]));
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
/*
|
||||
* NotifiesUserAboutNewIpAddress.php
|
||||
* Copyright (c) 2026 james@firefly-iii.org
|
||||
*
|
||||
* This file is part of Firefly III (https://github.com/firefly-iii).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace FireflyIII\Listeners\Security\User;
|
||||
|
||||
use Exception;
|
||||
use FireflyIII\Events\Security\User\UserLoggedInFromNewIpAddress;
|
||||
use FireflyIII\Notifications\User\UserLogin;
|
||||
use FireflyIII\Support\Facades\Preferences;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Notification;
|
||||
|
||||
class NotifiesUserAboutNewIpAddress
|
||||
{
|
||||
public function handle(UserLoggedInFromNewIpAddress $event): void {
|
||||
$user = $event->user;
|
||||
|
||||
if ($user->hasRole('demo')) {
|
||||
return; // do not email demo user.
|
||||
}
|
||||
|
||||
$list = Preferences::getForUser($user, 'login_ip_history', [])->data;
|
||||
if (!is_array($list)) {
|
||||
$list = [];
|
||||
}
|
||||
|
||||
/** @var array $entry */
|
||||
foreach ($list as $index => $entry) {
|
||||
if (false === $entry['notified']) {
|
||||
try {
|
||||
Notification::send($user, new UserLogin());
|
||||
} catch (Exception $e) {
|
||||
$message = $e->getMessage();
|
||||
if (str_contains($message, 'Bcc')) {
|
||||
Log::warning('[Bcc] Could not send notification. Please validate your email settings, use the .env.example file as a guide.');
|
||||
|
||||
return;
|
||||
}
|
||||
if (str_contains($message, 'RFC 2822')) {
|
||||
Log::warning('[RFC] Could not send notification. Please validate your email settings, use the .env.example file as a guide.');
|
||||
|
||||
return;
|
||||
}
|
||||
Log::error($e->getMessage());
|
||||
Log::error($e->getTraceAsString());
|
||||
}
|
||||
}
|
||||
$list[$index]['notified'] = true;
|
||||
}
|
||||
|
||||
Preferences::setForUser($user, 'login_ip_history', $list);
|
||||
}
|
||||
}
|
||||
81
app/Listeners/Security/User/StoresNewIpAddress.php
Normal file
81
app/Listeners/Security/User/StoresNewIpAddress.php
Normal file
@@ -0,0 +1,81 @@
|
||||
<?php
|
||||
/*
|
||||
* StoresNewIpAddress.php
|
||||
* Copyright (c) 2026 james@firefly-iii.org
|
||||
*
|
||||
* This file is part of Firefly III (https://github.com/firefly-iii).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace FireflyIII\Listeners\Security\User;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use FireflyIII\Events\Security\User\UserLoggedInFromNewIpAddress;
|
||||
use FireflyIII\Events\Security\User\UserSuccessfullyLoggedIn;
|
||||
use FireflyIII\Support\Facades\Preferences;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class StoresNewIpAddress
|
||||
{
|
||||
public function handle(UserSuccessfullyLoggedIn $event): void
|
||||
{
|
||||
Log::debug('Now in storeUserIPAddress');
|
||||
$user = $event->user;
|
||||
|
||||
if ($user->hasRole('demo')) {
|
||||
Log::debug('Do not log demo user logins');
|
||||
|
||||
return;
|
||||
}
|
||||
/** @var array $preference */
|
||||
$preference = Preferences::getForUser($user, 'login_ip_history', [])->data;
|
||||
$inArray = false;
|
||||
$ip = request()->ip();
|
||||
Log::debug(sprintf('User logging in from IP address %s', $ip));
|
||||
|
||||
// update array if in array
|
||||
foreach ($preference as $index => $row) {
|
||||
if ($row['ip'] === $ip) {
|
||||
Log::debug('Found IP in array, refresh time.');
|
||||
$preference[$index]['time'] = now(config('app.timezone'))->format('Y-m-d H:i:s');
|
||||
$inArray = true;
|
||||
}
|
||||
// clean up old entries (6 months)
|
||||
$carbon = Carbon::createFromFormat('Y-m-d H:i:s', $preference[$index]['time']);
|
||||
if ($carbon instanceof Carbon && $carbon->diffInMonths(today(), true) > 6) {
|
||||
Log::debug(sprintf('Entry for %s is very old, remove it.', $row['ip']));
|
||||
unset($preference[$index]);
|
||||
}
|
||||
}
|
||||
// add to array if not the case:
|
||||
if (false === $inArray) {
|
||||
$preference[] = [
|
||||
'ip' => $ip,
|
||||
'time' => now(config('app.timezone'))->format('Y-m-d H:i:s'),
|
||||
'notified' => false,
|
||||
];
|
||||
}
|
||||
$preference = array_values($preference);
|
||||
|
||||
/** @var bool $send */
|
||||
$send = Preferences::getForUser($user, 'notification_user_login', true)->data;
|
||||
Preferences::setForUser($user, 'login_ip_history', $preference);
|
||||
|
||||
if (false === $inArray && true === $send) {
|
||||
event(new UserLoggedInFromNewIpAddress($user));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
89
app/Listeners/Test/SendsTestNotification.php
Normal file
89
app/Listeners/Test/SendsTestNotification.php
Normal file
@@ -0,0 +1,89 @@
|
||||
<?php
|
||||
/*
|
||||
* SendsTestNotification.php
|
||||
* Copyright (c) 2026 james@firefly-iii.org
|
||||
*
|
||||
* This file is part of Firefly III (https://github.com/firefly-iii).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace FireflyIII\Listeners\Test;
|
||||
|
||||
use Exception;
|
||||
use FireflyIII\Events\Test\OwnerTestsNotificationChannel;
|
||||
use FireflyIII\Events\Test\UserTestsNotificationChannel;
|
||||
use FireflyIII\Notifications\Test\OwnerTestNotificationEmail;
|
||||
use FireflyIII\Notifications\Test\OwnerTestNotificationPushover;
|
||||
use FireflyIII\Notifications\Test\OwnerTestNotificationSlack;
|
||||
use FireflyIII\Notifications\Test\UserTestNotificationEmail;
|
||||
use FireflyIII\Notifications\Test\UserTestNotificationPushover;
|
||||
use FireflyIII\Notifications\Test\UserTestNotificationSlack;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Notification;
|
||||
|
||||
class SendsTestNotification
|
||||
{
|
||||
public function handle(OwnerTestsNotificationChannel | UserTestsNotificationChannel $event): void
|
||||
{
|
||||
Log::debug(sprintf('Now in SendsTestNotification::handle(%s->"%s")', get_class($event), $event->channel));
|
||||
|
||||
$type = str_contains(get_class($event), 'Owner') ? 'owner' : 'user';
|
||||
$key = sprintf('%s-%s', $type, $event->channel);
|
||||
switch ($key) {
|
||||
case 'user-email':
|
||||
$class = UserTestNotificationEmail::class;
|
||||
break;
|
||||
case 'user-slack':
|
||||
$class = UserTestNotificationSlack::class;
|
||||
break;
|
||||
case 'user-pushover':
|
||||
$class = UserTestNotificationPushover::class;
|
||||
break;
|
||||
case 'owner-email':
|
||||
$class = OwnerTestNotificationEmail::class;
|
||||
break;
|
||||
case 'owner-slack':
|
||||
$class = OwnerTestNotificationSlack::class;
|
||||
break;
|
||||
case 'owner-pushover':
|
||||
$class = OwnerTestNotificationPushover::class;
|
||||
break;
|
||||
default:
|
||||
Log::error(sprintf('Unknown key "%s" in sendTestNotification method.', $key));
|
||||
|
||||
return;
|
||||
}
|
||||
Log::debug(sprintf('Will send %s as a notification.', $class));
|
||||
|
||||
try {
|
||||
Notification::send($event->user, new $class());
|
||||
} catch (Exception $e) {
|
||||
$message = $e->getMessage();
|
||||
if (str_contains($message, 'Bcc')) {
|
||||
Log::warning('[Bcc] Could not send notification. Please validate your email settings, use the .env.example file as a guide.');
|
||||
|
||||
return;
|
||||
}
|
||||
if (str_contains($message, 'RFC 2822')) {
|
||||
Log::warning('[RFC] Could not send notification. Please validate your email settings, use the .env.example file as a guide.');
|
||||
|
||||
return;
|
||||
}
|
||||
Log::error($e->getMessage());
|
||||
Log::error($e->getTraceAsString());
|
||||
}
|
||||
Log::debug(sprintf('If you see no errors above this line, test notification was sent over channel "%s"', $event->channel));
|
||||
}
|
||||
}
|
||||
@@ -23,10 +23,8 @@ declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Providers;
|
||||
|
||||
use FireflyIII\Events\ActuallyLoggedIn;
|
||||
use FireflyIII\Events\Admin\InvitationCreated;
|
||||
use FireflyIII\Events\DestroyedTransactionGroup;
|
||||
use FireflyIII\Events\DetectedNewIPAddress;
|
||||
use FireflyIII\Events\Model\TransactionGroup\TriggeredStoredTransactionGroup;
|
||||
use FireflyIII\Events\NewVersionAvailable;
|
||||
use FireflyIII\Events\Preferences\UserGroupChangedPrimaryCurrency;
|
||||
@@ -37,8 +35,6 @@ use FireflyIII\Events\RequestedSendWebhookMessages;
|
||||
use FireflyIII\Events\RequestedVersionCheckStatus;
|
||||
use FireflyIII\Events\StoredAccount;
|
||||
use FireflyIII\Events\StoredTransactionGroup;
|
||||
use FireflyIII\Events\Test\OwnerTestNotificationChannel;
|
||||
use FireflyIII\Events\Test\UserTestNotificationChannel;
|
||||
use FireflyIII\Events\TriggeredAuditLog;
|
||||
use FireflyIII\Events\UpdatedAccount;
|
||||
use FireflyIII\Events\UpdatedTransactionGroup;
|
||||
@@ -73,12 +69,9 @@ class EventServiceProvider extends ServiceProvider
|
||||
'FireflyIII\Handlers\Events\UserEventHandler@checkSingleUserIsAdmin',
|
||||
'FireflyIII\Handlers\Events\UserEventHandler@demoUserBackToEnglish',
|
||||
],
|
||||
ActuallyLoggedIn::class => [
|
||||
'FireflyIII\Handlers\Events\UserEventHandler@storeUserIPAddress',
|
||||
],
|
||||
DetectedNewIPAddress::class => [
|
||||
'FireflyIII\Handlers\Events\UserEventHandler@notifyNewIPAddress',
|
||||
],
|
||||
// DetectedNewIPAddress::class => [
|
||||
// 'FireflyIII\Handlers\Events\UserEventHandler@notifyNewIPAddress',
|
||||
// ],
|
||||
RequestedVersionCheckStatus::class => [
|
||||
'FireflyIII\Handlers\Events\VersionCheckEventHandler@checkForUpdates',
|
||||
],
|
||||
@@ -90,18 +83,18 @@ class EventServiceProvider extends ServiceProvider
|
||||
RequestedNewPassword::class => [
|
||||
'FireflyIII\Handlers\Events\UserEventHandler@sendNewPassword',
|
||||
],
|
||||
UserTestNotificationChannel::class => [
|
||||
'FireflyIII\Handlers\Events\UserEventHandler@sendTestNotification',
|
||||
],
|
||||
// UserTestsNotificationChannel::class => [
|
||||
// 'FireflyIII\Handlers\Events\UserEventHandler@sendTestNotification',
|
||||
// ],
|
||||
// is a User related event.
|
||||
UserChangedEmail::class => [
|
||||
'FireflyIII\Handlers\Events\UserEventHandler@sendEmailChangeConfirmMail',
|
||||
'FireflyIII\Handlers\Events\UserEventHandler@sendEmailChangeUndoMail',
|
||||
],
|
||||
// admin related
|
||||
OwnerTestNotificationChannel::class => [
|
||||
'FireflyIII\Handlers\Events\AdminEventHandler@sendTestNotification',
|
||||
],
|
||||
// OwnerTestsNotificationChannel::class => [
|
||||
// 'FireflyIII\Handlers\Events\AdminEventHandler@sendTestNotification',
|
||||
// ],
|
||||
NewVersionAvailable::class => [
|
||||
'FireflyIII\Handlers\Events\AdminEventHandler@sendNewVersion',
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user