Unknown user warning.

This commit is contained in:
James Cole 2024-12-12 07:09:17 +01:00
parent d995bfc081
commit 0e5eb036b0
No known key found for this signature in database
GPG Key ID: B49A324B7EAD6D80
11 changed files with 252 additions and 23 deletions

View File

@ -0,0 +1,39 @@
<?php
/*
* UnknownUserAttemptedLogin.php
* Copyright (c) 2024 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/.
*/
declare(strict_types=1);
namespace FireflyIII\Events\Security;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Log;
class UnknownUserAttemptedLogin
{
use SerializesModels;
public string $address;
public function __construct(string $address)
{
$this->address = $address;
}
}

View File

@ -25,7 +25,9 @@ namespace FireflyIII\Handlers\Events;
use FireflyIII\Events\Admin\InvitationCreated;
use FireflyIII\Events\NewVersionAvailable;
use FireflyIII\Events\Security\UnknownUserAttemptedLogin;
use FireflyIII\Events\Test\TestNotificationChannel;
use FireflyIII\Notifications\Admin\UnknownUserLoginAttempt;
use FireflyIII\Notifications\Admin\UserInvitation;
use FireflyIII\Notifications\Admin\VersionCheckResult;
use FireflyIII\Notifications\Notifiables\OwnerNotifiable;
@ -41,6 +43,28 @@ use Illuminate\Support\Facades\Notification;
*/
class AdminEventHandler
{
public function sendLoginAttemptNotification(UnknownUserAttemptedLogin $event): void {
try {
$owner = new OwnerNotifiable();
Notification::send($owner, new UnknownUserLoginAttempt($event->address));
} catch (\Exception $e) { // @phpstan-ignore-line
$message = $e->getMessage();
if (str_contains($message, 'Bcc')) {
app('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')) {
app('log')->warning('[RFC] Could not send notification. Please validate your email settings, use the .env.example file as a guide.');
return;
}
app('log')->error($e->getMessage());
app('log')->error($e->getTraceAsString());
}
}
public function sendInvitationNotification(InvitationCreated $event): void
{
$sendMail = app('fireflyconfig')->get('notification_invite_created', true)->data;

View File

@ -25,9 +25,12 @@ namespace FireflyIII\Http\Controllers\Auth;
use Cookie;
use FireflyIII\Events\ActuallyLoggedIn;
use FireflyIII\Events\Security\UnknownUserAttemptedLogin;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Notifications\Notifiables\OwnerNotifiable;
use FireflyIII\Providers\RouteServiceProvider;
use FireflyIII\Repositories\User\UserRepositoryInterface;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Contracts\View\Factory;
use Illuminate\Contracts\View\View;
@ -57,6 +60,7 @@ class LoginController extends Controller
* Where to redirect users after login.
*/
protected string $redirectTo = RouteServiceProvider::HOME;
private UserRepositoryInterface $repository;
private string $username;
@ -68,6 +72,7 @@ class LoginController extends Controller
parent::__construct();
$this->username = 'email';
$this->middleware('guest')->except('logout');
$this->repository = app(UserRepositoryInterface::class);
}
/**
@ -122,6 +127,11 @@ class LoginController extends Controller
return $this->sendLoginResponse($request);
}
app('log')->warning('Login attempt failed.');
$username = (string) $request->get($this->username());
if(null === $this->repository->findByEmail($username)) {
// send event to owner.
event(new UnknownUserAttemptedLogin($username));
}
// Copied directly from AuthenticatesUsers, but with logging added:
// If the login attempt was unsuccessful we will increment the number of attempts

View File

@ -25,7 +25,6 @@ namespace FireflyIII\Http\Controllers;
use Carbon\Carbon;
use Carbon\Exceptions\InvalidFormatException;
use FireflyIII\Events\NewVersionAvailable;
use FireflyIII\Events\RequestedVersionCheckStatus;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Helpers\Collector\GroupCollectorInterface;

View File

@ -111,9 +111,10 @@ class PreferencesController extends Controller
// notification preferences (single value for each):
$notifications = [];
die('fix the reference to the available notifications.');
foreach (config('firefly.available_notifications') as $notification) {
$notifications[$notification] = app('preferences')->get(sprintf('notification_%s', $notification), true)->data;
foreach (config('notifications.notifications.user') as $key => $info) {
if($info['enabled']) {
$notifications[$key] = app('preferences')->get(sprintf('notification_%s', $key), true)->data;
}
}
ksort($languages);

View File

@ -0,0 +1,128 @@
<?php
/*
* UnknownUserLoginAttempt.php
* Copyright (c) 2024 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/.
*/
declare(strict_types=1);
namespace FireflyIII\Notifications\Admin;
use FireflyIII\Notifications\Notifiables\OwnerNotifiable;
use FireflyIII\Notifications\ReturnsAvailableChannels;
use FireflyIII\Notifications\ReturnsSettings;
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 UnknownUserLoginAttempt extends Notification
{
use Queueable;
private string $address;
/**
* Create a new notification instance.
*/
public function __construct(string $address)
{
$this->address = $address;
}
/**
* Get the array representation of the notification.
*
* @param mixed $notifiable
*
* @return array
*
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
public function toArray(OwnerNotifiable $notifiable)
{
return [
];
}
/**
* Get the mail representation of the notification.
*
* @return MailMessage
*
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
public function toMail(OwnerNotifiable $notifiable): MailMessage
{
return new MailMessage()
->markdown('emails.owner.unknown-user', ['address' => $this->address])
->subject((string) trans('email.unknown_user_subject'));
}
/**
* Get the Slack representation of the notification.
*
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
public function toSlack(OwnerNotifiable $notifiable): SlackMessage
{
return new SlackMessage()->content(
(string) trans('email.unknown_user_body', ['address' => $this->address])
);
}
public function toPushover(OwnerNotifiable $notifiable): PushoverMessage
{
return PushoverMessage::create((string) trans('email.unknown_user_message', ['address' => $this->address]))
->title((string) trans('email.unknown_user_subject'));
}
public function toNtfy(OwnerNotifiable $notifiable): Message
{
$settings = ReturnsSettings::getSettings('ntfy', 'owner', null);
// overrule config.
config(['ntfy-notification-channel.server' => $settings['ntfy_server']]);
config(['ntfy-notification-channel.topic' => $settings['ntfy_topic']]);
if ($settings['ntfy_auth']) {
// overrule auth as well.
config(['ntfy-notification-channel.authentication.enabled' => true]);
config(['ntfy-notification-channel.authentication.username' => $settings['ntfy_user']]);
config(['ntfy-notification-channel.authentication.password' => $settings['ntfy_pass']]);
}
$message = new Message();
$message->topic($settings['ntfy_topic']);
$message->title((string) trans('email.unknown_user_subject'));
$message->body((string) trans('email.unknown_user_message', ['address' => $this->address]));
return $message;
}
/**
* Get the notification's delivery channels.
*
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
public function via(OwnerNotifiable $notifiable)
{
return ReturnsAvailableChannels::returnChannels('owner');
}
}

View File

@ -47,6 +47,7 @@ use FireflyIII\Events\Security\MFABackupNoLeft;
use FireflyIII\Events\Security\MFAManyFailedAttempts;
use FireflyIII\Events\Security\MFANewBackupCodes;
use FireflyIII\Events\Security\MFAUsedBackupCode;
use FireflyIII\Events\Security\UnknownUserAttemptedLogin;
use FireflyIII\Events\StoredAccount;
use FireflyIII\Events\StoredTransactionGroup;
use FireflyIII\Events\Test\TestNotificationChannel;
@ -146,6 +147,9 @@ class EventServiceProvider extends ServiceProvider
'FireflyIII\Handlers\Events\AdminEventHandler@sendInvitationNotification',
'FireflyIII\Handlers\Events\UserEventHandler@sendRegistrationInvite',
],
UnknownUserAttemptedLogin::class => [
'FireflyIII\Handlers\Events\AdminEventHandler@sendLoginAttemptNotification',
],
// is a Transaction Journal related event.
StoredTransactionGroup::class => [

View File

@ -31,11 +31,18 @@ return [
],
'notifications' => [
'user' => [
'some_notification' => [
'enabled' => true,
'email' => '',
'slack' => '',
],
'bill_reminder' => ['enabled' => true, 'configurable' => true],
'new_access_token' => ['enabled' => true, 'configurable' => true],
'transaction_creation' => ['enabled' => true, 'configurable' => true],
'user_login' => ['enabled' => true, 'configurable' => true],
'rule_action_failures' => ['enabled' => true, 'configurable' => true],
'new_password' => ['enabled' => true, 'configurable' => false],
'enabled_mfa' => ['enabled' => true, 'configurable' => false],
'disabled_mfa' => ['enabled' => true, 'configurable' => false],
'few_left_mfa' => ['enabled' => true, 'configurable' => false],
'no_left_mfa' => ['enabled' => true, 'configurable' => false],
'many_failed_mfa' => ['enabled' => true, 'configurable' => false],
'new_backup_codes' => ['enabled' => true, 'configurable' => false],
],
'owner' => [
//'invitation_created' => ['enabled' => true],
@ -45,6 +52,7 @@ return [
'new_version' => ['enabled' => true],
'invite_created' => ['enabled' => true],
'invite_redeemed' => ['enabled' => true],
'unknown_user_attempt' => ['enabled' => true],
],
],
// // notifications

View File

@ -61,6 +61,11 @@ return [
'access_token_created_explanation' => 'With this token, they can access **all** of your financial records through the Firefly III API.',
'access_token_created_revoke' => 'If this wasn\'t you, please revoke this token as soon as possible at :url',
// unknown user login attempt
'unknown_user_subject' => 'An unknown user tried to log in',
'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".',
// registered
'registered_subject' => 'Welcome to Firefly III!',
'registered_subject_admin' => 'A new user has registered',

View File

@ -1376,7 +1376,14 @@ return [
'pref_notification_new_access_token' => 'Alert when a new API access token is created',
'pref_notification_transaction_creation' => 'Alert when a transaction is created automatically',
'pref_notification_user_login' => 'Alert when you login from a new location',
'pref_notification_rule_action_failures' => 'Alert when rule actions fail to execute (Slack or Discord only)',
'pref_notification_rule_action_failures' => 'Alert when rule actions fail to execute (not over email)',
'pref_notification_new_password' => 'Your password changed',
'pref_notification_enabled_mfa' => 'Multi factor authentication is enabled',
'pref_notification_disabled_mfa' => 'Multi factor authentication is disabled',
'pref_notification_few_left_mfa' => 'You have just a few backup codes left',
'pref_notification_no_left_mfa' => 'You have no backup codes left',
'pref_notification_many_failed_mfa' => 'The multi factor authentication check keeps failing',
'pref_notification_new_backup_codes' => 'New backup codes have been generated',
'pref_notifications' => 'Notifications',
'pref_notifications_help' => 'Indicate if these are notifications you would like to get. Some notifications may contain sensitive financial information.',
'slack_webhook_url' => 'Slack Webhook URL',
@ -2492,6 +2499,7 @@ return [
'owner_notification_check_new_version' => 'A new version is available',
'owner_notification_check_invite_created' => 'A user is invited to Firefly III',
'owner_notification_check_invite_redeemed' => 'A user invitation is redeemed',
'owner_notification_check_unknown_user_attempt' => 'An unknown user tries to login',
'all_invited_users' => 'All invited users',
'save_notification_settings' => 'Save settings',
'notification_settings' => 'Settings for notifications',

View File

@ -0,0 +1,3 @@
@component('mail::message')
{{ trans('email.unknown_user_body', ['address' => $address]) }}
@endcomponent