This commit is contained in:
James Cole 2020-08-28 21:58:03 +02:00
parent 7f48043505
commit 798c73394d
No known key found for this signature in database
GPG Key ID: B5669F9493CDE38D
8 changed files with 216 additions and 2 deletions

View File

@ -0,0 +1,49 @@
<?php
/*
* DetectedNewIPAddress.php
* Copyright (c) 2020 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\Events;
use FireflyIII\User;
use Illuminate\Queue\SerializesModels;
/**
* Class DetectedNewIPAddress
*/
class DetectedNewIPAddress extends Event
{
use SerializesModels;
public string $ipAddress;
public User $user;
/**
* Create a new event instance. This event is triggered when a new user registers.
*
* @param User $user
* @param string $ipAddress
*/
public function __construct(User $user, string $ipAddress)
{
$this->ipAddress = $ipAddress;
$this->user = $user;
}
}

View File

@ -23,11 +23,14 @@ declare(strict_types=1);
namespace FireflyIII\Handlers\Events;
use Carbon\Carbon;
use Exception;
use FireflyIII\Events\DetectedNewIPAddress;
use FireflyIII\Events\RegisteredUser;
use FireflyIII\Events\RequestedNewPassword;
use FireflyIII\Events\UserChangedEmail;
use FireflyIII\Mail\ConfirmEmailChangeMail;
use FireflyIII\Mail\NewIPAddressWarningMail;
use FireflyIII\Mail\RegisteredUser as RegisteredUserMail;
use FireflyIII\Mail\RequestedNewPassword as RequestedNewPasswordMail;
use FireflyIII\Mail\UndoEmailChangeMail;
@ -125,6 +128,78 @@ class UserEventHandler
return true;
}
/**
* @param Login $event
*/
public function storeUserIPAddress(Login $event): void
{
/** @var User $user */
$user = $event->user;
/** @var array $preference */
$preference = app('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->diffInMonths(today()) > 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);
app('preferences')->setForUser($user, 'login_ip_history', $preference);
if (false === $inArray) {
event(new DetectedNewIPAddress($user, $ip));
}
}
/**
* @param DetectedNewIPAddress $event
*/
public function notifyNewIPAddress(DetectedNewIPAddress $event): void
{
$user = $event->user;
$email = $user->email;
$ipAddress = $event->ipAddress;
$list = app('preferences')->getForUser($user, 'login_ip_history', [])->data;
/** @var array $entry */
foreach ($list as $index => $entry) {
if (false === $entry['notified']) {
try {
Mail::to($email)->send(new NewIPAddressWarningMail($ipAddress));
// @codeCoverageIgnoreStart
} catch (Exception $e) {
Log::error($e->getMessage());
}
}
$list[$index]['notified'] = true;
}
app('preferences')->setForUser($user, 'login_ip_history', $list);
}
/**
* Send email to confirm email change.
*
@ -167,7 +242,7 @@ class UserEventHandler
$ipAddress = $event->ipAddress;
$token = app('preferences')->getForUser($user, 'email_change_undo_token', 'invalid');
$hashed = hash('sha256', sprintf('%s%s', (string) config('app.key'), $oldEmail));
$uri = route('profile.undo-email-change', [$token->data,$hashed]);
$uri = route('profile.undo-email-change', [$token->data, $hashed]);
try {
Mail::to($oldEmail)->send(new UndoEmailChangeMail($newEmail, $oldEmail, $uri, $ipAddress));
// @codeCoverageIgnoreStart

View File

@ -0,0 +1,59 @@
<?php
/*
* NewIPAddressWarningMail.php
* Copyright (c) 2020 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\Mail;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
use Laravel\Passport\Client;
/**
* Class NewIPAddressWarningMail
*/
class NewIPAddressWarningMail extends Mailable
{
use Queueable, SerializesModels;
public string $ipAddress;
/**
* OAuthTokenCreatedMail constructor.
*
* @param string $ipAddress
*/
public function __construct(string $ipAddress)
{
$this->ipAddress = $ipAddress;
}
/**
* Build the message.
*
* @return $this
*/
public function build(): self
{
return $this->view('emails.new-ip-html')->text('emails.new-ip-text')
->subject((string) trans('email.login_from_new_ip'));
}
}

View File

@ -24,6 +24,7 @@ namespace FireflyIII\Providers;
use Exception;
use FireflyIII\Events\AdminRequestedTestMessage;
use FireflyIII\Events\DetectedNewIPAddress;
use FireflyIII\Events\RegisteredUser;
use FireflyIII\Events\RequestedNewPassword;
use FireflyIII\Events\RequestedReportOnJournals;
@ -67,6 +68,10 @@ class EventServiceProvider extends ServiceProvider
Login::class => [
'FireflyIII\Handlers\Events\UserEventHandler@checkSingleUserIsAdmin',
'FireflyIII\Handlers\Events\UserEventHandler@demoUserBackToEnglish',
'FireflyIII\Handlers\Events\UserEventHandler@storeUserIPAddress',
],
DetectedNewIPAddress::class => [
'FireflyIII\Handlers\Events\UserEventHandler@notifyNewIPAddress',
],
RequestedVersionCheckStatus::class => [
'FireflyIII\Handlers\Events\VersionCheckEventHandler@checkForUpdates',

View File

@ -51,7 +51,6 @@ class RemoteUserGuard implements Guard
*/
public function __construct(UserProvider $provider, Application $app)
{
Log::debug('Constructed RemoteUserGuard');
$this->application = $app;
$this->provider = $provider;
$this->user = null;

View File

@ -33,6 +33,11 @@ return [
'admin_test_subject' => 'A test message from your Firefly III installation',
'admin_test_body' => 'This is a test message from your Firefly III instance. It was sent to :email.',
// new IP
'login_from_new_ip' => 'You logged in from an unknown IP address.',
'new_ip_body' => 'You logged in from a new, unknown IP address:',
'new_ip_warning' => 'If you didn\'t 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!',
// access token created
'access_token_created_subject' => 'A new access token was created',
'access_token_created_body' => 'Somebody (hopefully you) just created a new Firefly III API Access Token for your user account.',

View File

@ -0,0 +1,14 @@
{% include 'emails.header-html' %}
<p style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;font-size:13px;">
{{ trans('email.new_ip_body') }}
</p>
<p style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;font-size:13px;font-weight: bold;">
{{ ipAddress }}
</p>
<p style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;font-size:13px;">
{{ trans('email.new_ip_warning') }}
</p>
{% include 'emails.footer-html' %}

View File

@ -0,0 +1,8 @@
{% include 'emails.header-text' %}
{{ trans('email.new_ip_body') }}
{{ ipAddress }}
{{ trans('email.new_ip_warning') }}
{% include 'emails.footer-text' %}