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/app/Http/Controllers/Auth/LoginController.php b/app/Http/Controllers/Auth/LoginController.php
index 9d44665779d0..f7d4fa946074 100644
--- a/app/Http/Controllers/Auth/LoginController.php
+++ b/app/Http/Controllers/Auth/LoginController.php
@@ -13,17 +13,22 @@ 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;
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;
use App\Utils\Traits\UserSessionAttributes;
use Google_Client;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
@@ -170,6 +175,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'))
@@ -228,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/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..266113b26909 100644
--- a/app/Listeners/User/UpdateUserLastLogin.php
+++ b/app/Listeners/User/UpdateUserLastLogin.php
@@ -11,7 +11,13 @@
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;
@@ -38,11 +44,36 @@ 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();
+ }
+
+ SystemLogger::dispatch(
+ $ip,
+ SystemLog::CATEGORY_SECURITY,
+ SystemLog::EVENT_USER,
+ SystemLog::TYPE_LOGIN_SUCCESS,
+ $event->company->clients()->first(),
+ );
+
}
}
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]]);
}
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/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',
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),
];
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
+
+
{!! $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