From 26fb1d09a53c4697d95fa661183536c41cbb4644 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Wed, 12 May 2021 14:21:44 +1000 Subject: [PATCH 1/4] Attach company documents if document attachments are enabled --- app/Mail/Engine/CreditEmailEngine.php | 5 ++++- app/Mail/Engine/InvoiceEmailEngine.php | 7 ++++++- app/Mail/Engine/QuoteEmailEngine.php | 5 ++++- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/app/Mail/Engine/CreditEmailEngine.php b/app/Mail/Engine/CreditEmailEngine.php index d9915c1c8d3e..286371b85800 100644 --- a/app/Mail/Engine/CreditEmailEngine.php +++ b/app/Mail/Engine/CreditEmailEngine.php @@ -105,7 +105,10 @@ class CreditEmailEngine extends BaseEmailEngine // Storage::url foreach($this->credit->documents as $document){ - // $this->setAttachments(['path'=>$document->filePath(),'name'=>$document->name]); + $this->setAttachments([['path' => $document->filePath(), 'name' => $document->name, 'mime' => $document->type]]); + } + + foreach($this->credit->company->documents as $document){ $this->setAttachments([['path' => $document->filePath(), 'name' => $document->name, 'mime' => $document->type]]); } diff --git a/app/Mail/Engine/InvoiceEmailEngine.php b/app/Mail/Engine/InvoiceEmailEngine.php index 06a4bfb41d4b..d6ee2258e250 100644 --- a/app/Mail/Engine/InvoiceEmailEngine.php +++ b/app/Mail/Engine/InvoiceEmailEngine.php @@ -116,10 +116,15 @@ class InvoiceEmailEngine extends BaseEmailEngine // Storage::url foreach($this->invoice->documents as $document){ - // $this->setAttachments(['path'=>$document->filePath(),'name'=>$document->name]); $this->setAttachments([['path' => $document->filePath(), 'name' => $document->name, 'mime' => $document->type]]); } + foreach($this->invoice->company->documents as $document){ + $this->setAttachments([['path' => $document->filePath(), 'name' => $document->name, 'mime' => $document->type]]); + } + + + } return $this; diff --git a/app/Mail/Engine/QuoteEmailEngine.php b/app/Mail/Engine/QuoteEmailEngine.php index a0709f6d775a..28519cb375fe 100644 --- a/app/Mail/Engine/QuoteEmailEngine.php +++ b/app/Mail/Engine/QuoteEmailEngine.php @@ -107,7 +107,10 @@ class QuoteEmailEngine extends BaseEmailEngine // Storage::url foreach($this->quote->documents as $document){ - // $this->setAttachments(['path'=>$document->filePath(),'name'=>$document->name]); + $this->setAttachments([['path' => $document->filePath(), 'name' => $document->name, 'mime' => $document->type]]); + } + + foreach($this->quote->company->documents as $document){ $this->setAttachments([['path' => $document->filePath(), 'name' => $document->name, 'mime' => $document->type]]); } From b780b636d69f1c1eaf6e54b0507d5f1f1e89d9f7 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Wed, 12 May 2021 15:36:23 +1000 Subject: [PATCH 2/4] Add flag to disable internal queue --- app/Console/Kernel.php | 2 +- config/ninja.php | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index d0e6ec2a74c9..4edd36a1c3ec 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -68,7 +68,7 @@ class Kernel extends ConsoleKernel } - if(config('queue.default') == 'database' && Ninja::isSelfHost()) { + if(config('queue.default') == 'database' && Ninja::isSelfHost() && config('ninja.internal_queue_enabled')) { $schedule->command('queue:work')->everyMinute()->withoutOverlapping(); $schedule->command('queue:restart')->everyFiveMinutes()->withoutOverlapping(); diff --git a/config/ninja.php b/config/ninja.php index 57f4771c6438..c50971c5344c 100644 --- a/config/ninja.php +++ b/config/ninja.php @@ -150,4 +150,5 @@ return [ 'ninja_stripe_key' => env('NINJA_STRIPE_KEY', null), 'ninja_stripe_publishable_key' => env('NINJA_PUBLISHABLE_KEY', null), 'pdf_generator' => env('PDF_GENERATOR', false), + 'internal_queue_enabled' => env('INTERNAL_QUEUE_ENABLED', true), ]; From f60b9c30ebc02514cbc7a4556b496013a6650cc2 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Wed, 12 May 2021 16:18:32 +1000 Subject: [PATCH 3/4] Add new login notification --- app/Http/Controllers/Auth/LoginController.php | 4 ++ app/Http/Middleware/TokenAuth.php | 2 - app/Listeners/User/UpdateUserLastLogin.php | 23 +++++++- app/Mail/Import/ImportCompleted.php | 9 +++ app/Mail/Migration/MaxCompanies.php | 10 ++++ app/Mail/User/UserLoggedIn.php | 59 +++++++++++++++++++ resources/lang/en/texts.php | 2 + .../views/email/admin/notification.blade.php | 18 ++++++ 8 files changed, 124 insertions(+), 3 deletions(-) create mode 100644 app/Mail/User/UserLoggedIn.php create mode 100644 resources/views/email/admin/notification.blade.php diff --git a/app/Http/Controllers/Auth/LoginController.php b/app/Http/Controllers/Auth/LoginController.php index 9d44665779d0..64dbfd4b823b 100644 --- a/app/Http/Controllers/Auth/LoginController.php +++ b/app/Http/Controllers/Auth/LoginController.php @@ -13,6 +13,7 @@ namespace App\Http\Controllers\Auth; use App\DataMapper\Analytics\LoginFailure; use App\DataMapper\Analytics\LoginSuccess; +use App\Events\User\UserLoggedIn; use App\Http\Controllers\BaseController; use App\Http\Controllers\Controller; use App\Jobs\Account\CreateAccount; @@ -24,6 +25,7 @@ use App\Models\CompanyToken; use App\Models\CompanyUser; use App\Models\User; use App\Transformers\CompanyUserTransformer; +use App\Utils\Ninja; use App\Utils\Traits\UserSessionAttributes; use Google_Client; use Illuminate\Foundation\Auth\AuthenticatesUsers; @@ -170,6 +172,8 @@ class LoginController extends BaseController $user = $this->guard()->user(); + event(new UserLoggedIn($user, $user->account->default_company, Ninja::eventVars($user->id))); + //if user has 2fa enabled - lets check this now: if($user->google_2fa_secret && $request->has('one_time_password')) diff --git a/app/Http/Middleware/TokenAuth.php b/app/Http/Middleware/TokenAuth.php index b93f7c90479e..bcea2cb8d26e 100644 --- a/app/Http/Middleware/TokenAuth.php +++ b/app/Http/Middleware/TokenAuth.php @@ -68,8 +68,6 @@ class TokenAuth //stateless, don't remember the user. auth()->login($user, false); - event(new UserLoggedIn($user, $company_token->company, Ninja::eventVars())); - } else { $error = [ 'message' => 'Invalid token', diff --git a/app/Listeners/User/UpdateUserLastLogin.php b/app/Listeners/User/UpdateUserLastLogin.php index 6bb1d37d11ff..ed833a9cbac1 100644 --- a/app/Listeners/User/UpdateUserLastLogin.php +++ b/app/Listeners/User/UpdateUserLastLogin.php @@ -11,7 +11,10 @@ namespace App\Listeners\User; +use App\Jobs\Mail\NinjaMailerJob; +use App\Jobs\Mail\NinjaMailerObject; use App\Libraries\MultiDB; +use App\Mail\User\UserLoggedIn; use Illuminate\Broadcasting\InteractsWithSockets; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Events\Dispatchable; @@ -38,11 +41,29 @@ class UpdateUserLastLogin implements ShouldQueue */ public function handle($event) { + MultiDB::setDb($event->company->db); $user = $event->user; - $user->last_login = now(); $user->save(); + + $event_vars = $event->event_vars; + $ip = array_key_exists('ip', $event->event_vars) ? $event->event_vars['ip'] : 'IP address not resolved'; + + if($user->ip != $ip) + { + $nmo = new NinjaMailerObject; + $nmo->mailable = new UserLoggedIn($user, $user->account->companies()->first(), $ip); + $nmo->company = $user->account->companies()->first(); + $nmo->settings = $user->account->companies()->first()->settings; + $nmo->to_user = $user; + NinjaMailerJob::dispatch($nmo); + + $user->ip = $ip; + $user->save(); + } + + } } diff --git a/app/Mail/Import/ImportCompleted.php b/app/Mail/Import/ImportCompleted.php index ecb50e51487f..8396fda29903 100644 --- a/app/Mail/Import/ImportCompleted.php +++ b/app/Mail/Import/ImportCompleted.php @@ -1,4 +1,13 @@ whitelabel = $this->company->account->isPaid(); return $this->from(config('mail.from.address'), config('mail.from.name')) + ->subject(ctrans('texts.max_companies')) ->view('email.migration.max_companies'); } } diff --git a/app/Mail/User/UserLoggedIn.php b/app/Mail/User/UserLoggedIn.php new file mode 100644 index 000000000000..7eade000111e --- /dev/null +++ b/app/Mail/User/UserLoggedIn.php @@ -0,0 +1,59 @@ +company = $company; + $this->user = $user; + $this->ip = $ip; + } + + /** + * Build the message. + * + * @return $this + */ + public function build() + { + + return $this->from(config('mail.from.address'), config('mail.from.name')) + ->subject(ctrans('texts.new_login_detected')) + ->view('email.admin.notification') + ->with([ + 'settings' => $this->company->settings, + 'logo' => $this->company->present()->logo(), + 'title' => ctrans('texts.new_login_detected'), + 'body' => ctrans('texts.new_login_description', ['email' =>$this->user->email, 'ip' => $this->ip, 'time' => now()]), + 'whitelabel' => $this->company->account->isPaid(), + ]); + } +} diff --git a/resources/lang/en/texts.php b/resources/lang/en/texts.php index a10ad715f272..2a46a25013ba 100644 --- a/resources/lang/en/texts.php +++ b/resources/lang/en/texts.php @@ -4246,6 +4246,8 @@ $LANG = array( 'activity_102' => ':user archived recurring invoice :recurring_invoice', 'activity_103' => ':user deleted recurring invoice :recurring_invoice', 'activity_104' => ':user restored recurring invoice :recurring_invoice', + 'new_login_detected' => 'New login detected for your account.', + 'new_login_description' => 'You recently logged in to your Invoice Ninja account from a new location or device:

IP: :ip
Time: :time
Email: :email', ); return $LANG; diff --git a/resources/views/email/admin/notification.blade.php b/resources/views/email/admin/notification.blade.php new file mode 100644 index 000000000000..7565faf86e21 --- /dev/null +++ b/resources/views/email/admin/notification.blade.php @@ -0,0 +1,18 @@ +@component('email.template.master', ['design' => 'light', 'settings' => $settings]) + + @slot('header') + @include('email.components.header', ['logo' => $logo]) + @endslot + +

{!! $title !!}

+ +

{!! $body !!}

+ + @if(isset($whitelabel) && !$whitelabel) + @slot('footer') + @component('email.components.footer', ['url' => 'https://invoiceninja.com', 'url_text' => '© InvoiceNinja']) + For any info, please visit InvoiceNinja. + @endcomponent + @endslot + @endif +@endcomponent From ac771009818c279f2c217db43c575f9dc55b9b6c Mon Sep 17 00:00:00 2001 From: David Bomba Date: Wed, 12 May 2021 16:31:02 +1000 Subject: [PATCH 4/4] New login notification --- app/Http/Controllers/Auth/LoginController.php | 11 +++++++++++ app/Listeners/User/UpdateUserLastLogin.php | 10 ++++++++++ app/Models/SystemLog.php | 3 +++ 3 files changed, 24 insertions(+) diff --git a/app/Http/Controllers/Auth/LoginController.php b/app/Http/Controllers/Auth/LoginController.php index 64dbfd4b823b..f7d4fa946074 100644 --- a/app/Http/Controllers/Auth/LoginController.php +++ b/app/Http/Controllers/Auth/LoginController.php @@ -18,11 +18,14 @@ use App\Http\Controllers\BaseController; use App\Http\Controllers\Controller; use App\Jobs\Account\CreateAccount; use App\Jobs\Company\CreateCompanyToken; +use App\Jobs\Util\SystemLogger; use App\Libraries\MultiDB; use App\Libraries\OAuth\OAuth; use App\Libraries\OAuth\Providers\Google; +use App\Models\Client; use App\Models\CompanyToken; use App\Models\CompanyUser; +use App\Models\SystemLog; use App\Models\User; use App\Transformers\CompanyUserTransformer; use App\Utils\Ninja; @@ -232,6 +235,14 @@ class LoginController extends BaseController ->increment() ->batch(); + SystemLogger::dispatch( + request()->getClientIp(), + SystemLog::CATEGORY_SECURITY, + SystemLog::EVENT_USER, + SystemLog::TYPE_LOGIN_FAILURE, + Client::first(), + ); + $this->incrementLoginAttempts($request); return response() diff --git a/app/Listeners/User/UpdateUserLastLogin.php b/app/Listeners/User/UpdateUserLastLogin.php index ed833a9cbac1..266113b26909 100644 --- a/app/Listeners/User/UpdateUserLastLogin.php +++ b/app/Listeners/User/UpdateUserLastLogin.php @@ -13,8 +13,11 @@ namespace App\Listeners\User; use App\Jobs\Mail\NinjaMailerJob; use App\Jobs\Mail\NinjaMailerObject; +use App\Jobs\Util\SystemLogger; use App\Libraries\MultiDB; use App\Mail\User\UserLoggedIn; +use App\Models\Client; +use App\Models\SystemLog; use Illuminate\Broadcasting\InteractsWithSockets; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Events\Dispatchable; @@ -64,6 +67,13 @@ class UpdateUserLastLogin implements ShouldQueue $user->save(); } + SystemLogger::dispatch( + $ip, + SystemLog::CATEGORY_SECURITY, + SystemLog::EVENT_USER, + SystemLog::TYPE_LOGIN_SUCCESS, + $event->company->clients()->first(), + ); } } diff --git a/app/Models/SystemLog.php b/app/Models/SystemLog.php index 3ad12a351cb1..c4d0e01bfed2 100644 --- a/app/Models/SystemLog.php +++ b/app/Models/SystemLog.php @@ -78,6 +78,9 @@ class SystemLog extends Model const TYPE_MODIFIED = 701; const TYPE_DELETED = 702; + const TYPE_LOGIN_SUCCESS = 800; + const TYPE_LOGIN_FAILURE = 801; + protected $fillable = [ 'client_id', 'company_id',