diff --git a/app/Events/Security/UserAttemptedLogin.php b/app/Events/Security/UserAttemptedLogin.php index 8457a5ecc3..55dc3af6a9 100644 --- a/app/Events/Security/UserAttemptedLogin.php +++ b/app/Events/Security/UserAttemptedLogin.php @@ -24,6 +24,7 @@ declare(strict_types=1); namespace FireflyIII\Events\Security; use FireflyIII\Events\Event; +use FireflyIII\User; use Illuminate\Contracts\Auth\Authenticatable; use Illuminate\Queue\SerializesModels; diff --git a/app/Handlers/Events/UserEventHandler.php b/app/Handlers/Events/UserEventHandler.php index 91b63b4e18..3eaa2dd6ea 100644 --- a/app/Handlers/Events/UserEventHandler.php +++ b/app/Handlers/Events/UserEventHandler.php @@ -43,6 +43,7 @@ use FireflyIII\Models\GroupMembership; use FireflyIII\Models\UserGroup; use FireflyIII\Models\UserRole; use FireflyIII\Notifications\Admin\UserRegistration as AdminRegistrationNotification; +use FireflyIII\Notifications\Security\UserFailedLoginAttempt; use FireflyIII\Notifications\Test\OwnerTestNotificationEmail; use FireflyIII\Notifications\Test\OwnerTestNotificationNtfy; use FireflyIII\Notifications\Test\OwnerTestNotificationPushover; diff --git a/app/Http/Controllers/Auth/LoginController.php b/app/Http/Controllers/Auth/LoginController.php index 6558e626de..8ef53caf16 100644 --- a/app/Http/Controllers/Auth/LoginController.php +++ b/app/Http/Controllers/Auth/LoginController.php @@ -26,6 +26,7 @@ namespace FireflyIII\Http\Controllers\Auth; use Cookie; use FireflyIII\Events\ActuallyLoggedIn; use FireflyIII\Events\Security\UnknownUserAttemptedLogin; +use FireflyIII\Events\Security\UserAttemptedLogin; use FireflyIII\Exceptions\FireflyException; use FireflyIII\Http\Controllers\Controller; use FireflyIII\Providers\RouteServiceProvider; diff --git a/app/Notifications/Admin/UserInvitation.php b/app/Notifications/Admin/UserInvitation.php index 8ac48334aa..7f9b90a0e0 100644 --- a/app/Notifications/Admin/UserInvitation.php +++ b/app/Notifications/Admin/UserInvitation.php @@ -103,7 +103,7 @@ class UserInvitation extends Notification */ public function toSlack(OwnerNotifiable $notifiable) { - return (new SlackMessage())->content( + return new SlackMessage()->content( (string) trans('email.invitation_created_body', ['email' => $this->invitee->user->email, 'invitee' => $this->invitee->email]) ); } diff --git a/app/Notifications/Admin/UserRegistration.php b/app/Notifications/Admin/UserRegistration.php index 90af37ff63..5a6e505da0 100644 --- a/app/Notifications/Admin/UserRegistration.php +++ b/app/Notifications/Admin/UserRegistration.php @@ -103,7 +103,7 @@ class UserRegistration extends Notification */ public function toSlack(OwnerNotifiable $notifiable) { - return (new SlackMessage())->content((string) trans('email.admin_new_user_registered', ['email' => $this->user->email, 'id' => $this->user->id])); + return new SlackMessage()->content((string) trans('email.admin_new_user_registered', ['email' => $this->user->email, 'id' => $this->user->id])); } /** diff --git a/app/Notifications/Admin/VersionCheckResult.php b/app/Notifications/Admin/VersionCheckResult.php index d809913e74..809504a546 100644 --- a/app/Notifications/Admin/VersionCheckResult.php +++ b/app/Notifications/Admin/VersionCheckResult.php @@ -100,7 +100,7 @@ class VersionCheckResult extends Notification */ public function toSlack(OwnerNotifiable $notifiable) { - return (new SlackMessage())->content($this->message) + return new SlackMessage()->content($this->message) ->attachment(static function ($attachment): void { $attachment->title('Firefly III @ GitHub', 'https://github.com/firefly-iii/firefly-iii/releases'); }); diff --git a/app/Notifications/Security/DisabledMFANotification.php b/app/Notifications/Security/DisabledMFANotification.php index de2917e711..f870510ce5 100644 --- a/app/Notifications/Security/DisabledMFANotification.php +++ b/app/Notifications/Security/DisabledMFANotification.php @@ -65,12 +65,9 @@ class DisabledMFANotification extends Notification return (new MailMessage())->markdown('emails.security.disabled-mfa', ['user' => $this->user])->subject($subject); } - /** - * @SuppressWarnings(PHPMD.UnusedFormalParameter) - */ - public function toNtfy(User $user): Message + public function toNtfy(User $notifiable): Message { - $settings = ReturnsSettings::getSettings('ntfy', 'user', $user); + $settings = ReturnsSettings::getSettings('ntfy', 'user', $notifiable); $message = new Message(); $message->topic($settings['ntfy_topic']); $message->title((string) trans('email.disabled_mfa_subject')); @@ -84,8 +81,6 @@ class DisabledMFANotification extends Notification */ public function toPushover(User $notifiable): PushoverMessage { - Log::debug('Now in (user) toPushover()'); - return PushoverMessage::create((string) trans('email.disabled_mfa_slack', ['email' => $this->user->email])) ->title((string) trans('email.disabled_mfa_subject')); } @@ -97,7 +92,7 @@ class DisabledMFANotification extends Notification { $message = (string) trans('email.disabled_mfa_slack', ['email' => $this->user->email]); - return (new SlackMessage())->content($message); + return new SlackMessage()->content($message); } /** diff --git a/app/Notifications/Security/EnabledMFANotification.php b/app/Notifications/Security/EnabledMFANotification.php index db6ba42652..2bc1c9858d 100644 --- a/app/Notifications/Security/EnabledMFANotification.php +++ b/app/Notifications/Security/EnabledMFANotification.php @@ -25,11 +25,14 @@ declare(strict_types=1); namespace FireflyIII\Notifications\Security; use FireflyIII\Notifications\ReturnsAvailableChannels; +use FireflyIII\Notifications\ReturnsSettings; use FireflyIII\User; use Illuminate\Bus\Queueable; use Illuminate\Notifications\Messages\MailMessage; use Illuminate\Notifications\Messages\SlackMessage; use Illuminate\Notifications\Notification; +use NotificationChannels\Pushover\PushoverMessage; +use Ntfy\Message; class EnabledMFANotification extends Notification { @@ -62,6 +65,26 @@ class EnabledMFANotification extends Notification return (new MailMessage())->markdown('emails.security.enabled-mfa', ['user' => $this->user])->subject($subject); } + public function toNtfy(User $notifiable): Message + { + $settings = ReturnsSettings::getSettings('ntfy', 'user', $notifiable); + $message = new Message(); + $message->topic($settings['ntfy_topic']); + $message->title((string) trans('email.enabled_mfa_subject')); + $message->body((string) trans('email.enabled_mfa_slack', ['email' => $this->user->email])); + + return $message; + } + + /** + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function toPushover(User $notifiable): PushoverMessage + { + return PushoverMessage::create((string) trans('email.enabled_mfa_slack', ['email' => $this->user->email])) + ->title((string) trans('email.enabled_mfa_subject')); + } + /** * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ @@ -69,7 +92,7 @@ class EnabledMFANotification extends Notification { $message = (string) trans('email.enabled_mfa_slack', ['email' => $this->user->email]); - return (new SlackMessage())->content($message); + return new SlackMessage()->content($message); } diff --git a/app/Notifications/Security/MFABackupFewLeftNotification.php b/app/Notifications/Security/MFABackupFewLeftNotification.php index f9b1c65e8e..cad845ed69 100644 --- a/app/Notifications/Security/MFABackupFewLeftNotification.php +++ b/app/Notifications/Security/MFABackupFewLeftNotification.php @@ -25,11 +25,14 @@ declare(strict_types=1); namespace FireflyIII\Notifications\Security; use FireflyIII\Notifications\ReturnsAvailableChannels; +use FireflyIII\Notifications\ReturnsSettings; use FireflyIII\User; use Illuminate\Bus\Queueable; use Illuminate\Notifications\Messages\MailMessage; use Illuminate\Notifications\Messages\SlackMessage; use Illuminate\Notifications\Notification; +use NotificationChannels\Pushover\PushoverMessage; +use Ntfy\Message; class MFABackupFewLeftNotification extends Notification { @@ -70,7 +73,27 @@ class MFABackupFewLeftNotification extends Notification { $message = (string) trans('email.mfa_few_backups_left_slack', ['email' => $this->user->email, 'count' => $this->count]); - return (new SlackMessage())->content($message); + return new SlackMessage()->content($message); + } + + public function toNtfy(User $notifiable): Message + { + $settings = ReturnsSettings::getSettings('ntfy', 'user', $notifiable); + $message = new Message(); + $message->topic($settings['ntfy_topic']); + $message->title((string) trans('email.mfa_few_backups_left_subject')); + $message->body((string) trans('email.mfa_few_backups_left_slack', ['email' => $this->user->email, 'count' => $this->count])); + + return $message; + } + + /** + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function toPushover(User $notifiable): PushoverMessage + { + return PushoverMessage::create((string) trans('email.mfa_few_backups_left_slack', ['email' => $this->user->email, 'count' => $this->count])) + ->title((string) trans('email.mfa_few_backups_left_subject')); } diff --git a/app/Notifications/Security/MFABackupNoLeftNotification.php b/app/Notifications/Security/MFABackupNoLeftNotification.php index ee53d2ecb7..a9389977bc 100644 --- a/app/Notifications/Security/MFABackupNoLeftNotification.php +++ b/app/Notifications/Security/MFABackupNoLeftNotification.php @@ -25,11 +25,14 @@ declare(strict_types=1); namespace FireflyIII\Notifications\Security; use FireflyIII\Notifications\ReturnsAvailableChannels; +use FireflyIII\Notifications\ReturnsSettings; use FireflyIII\User; use Illuminate\Bus\Queueable; use Illuminate\Notifications\Messages\MailMessage; use Illuminate\Notifications\Messages\SlackMessage; use Illuminate\Notifications\Notification; +use NotificationChannels\Pushover\PushoverMessage; +use Ntfy\Message; class MFABackupNoLeftNotification extends Notification { @@ -69,7 +72,27 @@ class MFABackupNoLeftNotification extends Notification { $message = (string) trans('email.mfa_no_backups_left_slack', ['email' => $this->user->email]); - return (new SlackMessage())->content($message); + return new SlackMessage()->content($message); + } + + public function toNtfy(User $notifiable): Message + { + $settings = ReturnsSettings::getSettings('ntfy', 'user', $notifiable); + $message = new Message(); + $message->topic($settings['ntfy_topic']); + $message->title((string) trans('email.mfa_no_backups_left_subject')); + $message->body((string) trans('email.mfa_no_backups_left_slack', ['email' => $this->user->email])); + + return $message; + } + + /** + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function toPushover(User $notifiable): PushoverMessage + { + return PushoverMessage::create((string) trans('email.mfa_few_backups_left_slack', ['email' => $this->user->email])) + ->title((string) trans('email.mfa_no_backups_left_slack')); } diff --git a/app/Notifications/Security/MFAManyFailedAttemptsNotification.php b/app/Notifications/Security/MFAManyFailedAttemptsNotification.php index f21bc3d337..54f3e5a9ad 100644 --- a/app/Notifications/Security/MFAManyFailedAttemptsNotification.php +++ b/app/Notifications/Security/MFAManyFailedAttemptsNotification.php @@ -25,11 +25,14 @@ declare(strict_types=1); namespace FireflyIII\Notifications\Security; use FireflyIII\Notifications\ReturnsAvailableChannels; +use FireflyIII\Notifications\ReturnsSettings; use FireflyIII\User; use Illuminate\Bus\Queueable; use Illuminate\Notifications\Messages\MailMessage; use Illuminate\Notifications\Messages\SlackMessage; use Illuminate\Notifications\Notification; +use NotificationChannels\Pushover\PushoverMessage; +use Ntfy\Message; class MFAManyFailedAttemptsNotification extends Notification { @@ -68,7 +71,27 @@ class MFAManyFailedAttemptsNotification extends Notification { $message = (string) trans('email.mfa_many_failed_slack', ['email' => $this->user->email, 'count' => $this->count]); - return (new SlackMessage())->content($message); + return new SlackMessage()->content($message); + } + + public function toNtfy(User $notifiable): Message + { + $settings = ReturnsSettings::getSettings('ntfy', 'user', $notifiable); + $message = new Message(); + $message->topic($settings['ntfy_topic']); + $message->title((string) trans('email.mfa_many_failed_subject')); + $message->body((string) trans('email.mfa_many_failed_slack', ['email' => $this->user->email, 'count' => $this->count])); + + return $message; + } + + /** + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function toPushover(User $notifiable): PushoverMessage + { + return PushoverMessage::create((string) trans('email.mfa_many_failed_slack', ['email' => $this->user->email, 'count' => $this->count])) + ->title((string) trans('email.mfa_many_failed_subject')); } diff --git a/app/Notifications/Security/MFAUsedBackupCodeNotification.php b/app/Notifications/Security/MFAUsedBackupCodeNotification.php index 30915d8fcd..f0680e72d5 100644 --- a/app/Notifications/Security/MFAUsedBackupCodeNotification.php +++ b/app/Notifications/Security/MFAUsedBackupCodeNotification.php @@ -25,11 +25,14 @@ declare(strict_types=1); namespace FireflyIII\Notifications\Security; use FireflyIII\Notifications\ReturnsAvailableChannels; +use FireflyIII\Notifications\ReturnsSettings; use FireflyIII\User; use Illuminate\Bus\Queueable; use Illuminate\Notifications\Messages\MailMessage; use Illuminate\Notifications\Messages\SlackMessage; use Illuminate\Notifications\Notification; +use NotificationChannels\Pushover\PushoverMessage; +use Ntfy\Message; class MFAUsedBackupCodeNotification extends Notification { @@ -69,9 +72,28 @@ class MFAUsedBackupCodeNotification extends Notification { $message = (string) trans('email.used_backup_code_slack', ['email' => $this->user->email]); - return (new SlackMessage())->content($message); + return new SlackMessage()->content($message); } + public function toNtfy(User $notifiable): Message + { + $settings = ReturnsSettings::getSettings('ntfy', 'user', $notifiable); + $message = new Message(); + $message->topic($settings['ntfy_topic']); + $message->title((string) trans('email.used_backup_code_subject')); + $message->body((string) trans('email.used_backup_code_slack', ['email' => $this->user->email])); + + return $message; + } + + /** + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function toPushover(User $notifiable): PushoverMessage + { + return PushoverMessage::create((string) trans('email.used_backup_code_slack', ['email' => $this->user->email])) + ->title((string) trans('email.used_backup_code_subject')); + } /** * @SuppressWarnings(PHPMD.UnusedFormalParameter) diff --git a/app/Notifications/Security/NewBackupCodesNotification.php b/app/Notifications/Security/NewBackupCodesNotification.php index 6fbe432cc2..196c577b74 100644 --- a/app/Notifications/Security/NewBackupCodesNotification.php +++ b/app/Notifications/Security/NewBackupCodesNotification.php @@ -25,11 +25,14 @@ declare(strict_types=1); namespace FireflyIII\Notifications\Security; use FireflyIII\Notifications\ReturnsAvailableChannels; +use FireflyIII\Notifications\ReturnsSettings; use FireflyIII\User; use Illuminate\Bus\Queueable; use Illuminate\Notifications\Messages\MailMessage; use Illuminate\Notifications\Messages\SlackMessage; use Illuminate\Notifications\Notification; +use NotificationChannels\Pushover\PushoverMessage; +use Ntfy\Message; class NewBackupCodesNotification extends Notification { @@ -69,7 +72,27 @@ class NewBackupCodesNotification extends Notification { $message = (string) trans('email.new_backup_codes_slack', ['email' => $this->user->email]); - return (new SlackMessage())->content($message); + return new SlackMessage()->content($message); + } + + public function toNtfy(User $notifiable): Message + { + $settings = ReturnsSettings::getSettings('ntfy', 'user', $notifiable); + $message = new Message(); + $message->topic($settings['ntfy_topic']); + $message->title((string) trans('email.new_backup_codes_subject')); + $message->body((string) trans('email.new_backup_codes_slack', ['email' => $this->user->email])); + + return $message; + } + + /** + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function toPushover(User $notifiable): PushoverMessage + { + return PushoverMessage::create((string) trans('email.new_backup_codes_slack', ['email' => $this->user->email])) + ->title((string) trans('email.new_backup_codes_subject')); } /** diff --git a/app/Notifications/Security/UserFailedLoginAttempt.php b/app/Notifications/Security/UserFailedLoginAttempt.php index 90308f1aac..c3827e45fd 100644 --- a/app/Notifications/Security/UserFailedLoginAttempt.php +++ b/app/Notifications/Security/UserFailedLoginAttempt.php @@ -24,11 +24,14 @@ declare(strict_types=1); namespace FireflyIII\Notifications\Security; use FireflyIII\Notifications\ReturnsAvailableChannels; +use FireflyIII\Notifications\ReturnsSettings; use FireflyIII\User; use Illuminate\Bus\Queueable; use Illuminate\Notifications\Messages\MailMessage; use Illuminate\Notifications\Messages\SlackMessage; use Illuminate\Notifications\Notification; +use NotificationChannels\Pushover\PushoverMessage; +use Ntfy\Message; class UserFailedLoginAttempt extends Notification { @@ -54,9 +57,9 @@ class UserFailedLoginAttempt extends Notification */ public function toMail(User $notifiable) { - $subject = (string) trans('email.new_backup_codes_subject'); + $subject = (string) trans('email.failed_login_subject'); - return (new MailMessage())->markdown('emails.security.new-backup-codes', ['user' => $this->user])->subject($subject); + return (new MailMessage())->markdown('emails.security.failed-login', ['user' => $this->user])->subject($subject); } /** @@ -64,9 +67,29 @@ class UserFailedLoginAttempt extends Notification */ public function toSlack(User $notifiable) { - $message = (string) trans('email.new_backup_codes_slack', ['email' => $this->user->email]); + $message = (string) trans('email.failed_login_message', ['email' => $this->user->email]); - return (new SlackMessage())->content($message); + return new SlackMessage()->content($message); + } + + public function toNtfy(User $notifiable): Message + { + $settings = ReturnsSettings::getSettings('ntfy', 'user', $notifiable); + $message = new Message(); + $message->topic($settings['ntfy_topic']); + $message->title((string) trans('email.failed_login_subject')); + $message->body((string) trans('email.failed_login_message', ['email' => $this->user->email])); + + return $message; + } + + /** + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function toPushover(User $notifiable): PushoverMessage + { + return PushoverMessage::create((string) trans('email.failed_login_message', ['email' => $this->user->email])) + ->title((string) trans('email.failed_login_subject')); } /** diff --git a/app/Notifications/User/BillReminder.php b/app/Notifications/User/BillReminder.php index 0fa3dedd5a..35b3f53047 100644 --- a/app/Notifications/User/BillReminder.php +++ b/app/Notifications/User/BillReminder.php @@ -88,7 +88,7 @@ class BillReminder extends Notification $bill = $this->bill; $url = route('bills.show', [$bill->id]); - return (new SlackMessage()) + return new SlackMessage() ->warning() ->attachment(static function ($attachment) use ($bill, $url): void { $attachment->title((string) trans('firefly.visit_bill', ['name' => $bill->name]), $url); diff --git a/app/Notifications/User/NewAccessToken.php b/app/Notifications/User/NewAccessToken.php index 4144b19bf6..16175a22ae 100644 --- a/app/Notifications/User/NewAccessToken.php +++ b/app/Notifications/User/NewAccessToken.php @@ -63,7 +63,7 @@ class NewAccessToken extends Notification */ public function toSlack(User $notifiable) { - return (new SlackMessage())->content((string) trans('email.access_token_created_body')); + return new SlackMessage()->content((string) trans('email.access_token_created_body')); } /** diff --git a/app/Notifications/User/RuleActionFailed.php b/app/Notifications/User/RuleActionFailed.php index 32c3a199a5..ea36540bb4 100644 --- a/app/Notifications/User/RuleActionFailed.php +++ b/app/Notifications/User/RuleActionFailed.php @@ -73,7 +73,7 @@ class RuleActionFailed extends Notification $ruleTitle = $this->ruleTitle; $ruleLink = $this->ruleLink; - return (new SlackMessage())->content($this->message)->attachment(static function ($attachment) use ($groupTitle, $groupLink): void { + return new SlackMessage()->content($this->message)->attachment(static function ($attachment) use ($groupTitle, $groupLink): void { $attachment->title((string) trans('rules.inspect_transaction', ['title' => $groupTitle]), $groupLink); })->attachment(static function ($attachment) use ($ruleTitle, $ruleLink): void { $attachment->title((string) trans('rules.inspect_rule', ['title' => $ruleTitle]), $ruleLink); diff --git a/app/Notifications/User/UserLogin.php b/app/Notifications/User/UserLogin.php index 82682e7c59..c0a06bbad2 100644 --- a/app/Notifications/User/UserLogin.php +++ b/app/Notifications/User/UserLogin.php @@ -94,7 +94,7 @@ class UserLogin extends Notification $host = $hostName; } - return (new SlackMessage())->content((string) trans('email.slack_login_from_new_ip', ['host' => $host, 'ip' => $this->ip])); + return new SlackMessage()->content((string) trans('email.slack_login_from_new_ip', ['host' => $host, 'ip' => $this->ip])); } /** diff --git a/resources/lang/en_US/email.php b/resources/lang/en_US/email.php index 087b24c9a5..408e75132a 100644 --- a/resources/lang/en_US/email.php +++ b/resources/lang/en_US/email.php @@ -66,6 +66,12 @@ return [ 'unknown_user_body' => 'An unknown user tried to log in to Firefly III. The email address they used was ":address".', 'unknown_user_message' => 'The email address they used was ":address".', + // known user login attempt + 'failed_login_subject' => 'Firefly III detected a failed login attempt', + 'failed_login_body' => 'Firefly III detected that somebody (you?) failed to login with your account ":email". Please verify that this was you.', + 'failed_login_message' => 'A failed login attempt on your Firefly III account ":email" was detected.', + 'failed_login_warning' => 'If you recognize this IP address or the login attempt, you can ignore this message. If you didn\'t try to login, of if you have no idea what this is about, verify your password security, change it, and log out all other sessions. To do this, go to your profile page. Of course you have 2FA enabled already, right? Stay safe!', + // registered 'registered_subject' => 'Welcome to Firefly III!', 'registered_subject_admin' => 'A new user has registered', diff --git a/resources/views/emails/security/failed-login.blade.php b/resources/views/emails/security/failed-login.blade.php new file mode 100644 index 0000000000..afe4ab48d2 --- /dev/null +++ b/resources/views/emails/security/failed-login.blade.php @@ -0,0 +1,6 @@ +@component('mail::message') +{{ trans('email.failed_login_body', ['email' => $user->email]) }} + +{{ trans('email.failed_login_warning') }} + +@endcomponent