Log all logins and associated devices with new section in settings 🔧 Secure login typo 🔧 AlreadyLogged redirection

This commit is contained in:
MarceauKa 2019-09-26 16:15:24 +02:00
parent 95d1ee4b75
commit 0b4246176c
15 changed files with 477 additions and 12 deletions

View File

@ -10,7 +10,9 @@ use App\Http\Requests\StoreSettingsRequest;
use App\Services\Import; use App\Services\Import;
use App\Services\Shaarli\Shaarli; use App\Services\Shaarli\Shaarli;
use App\Tag; use App\Tag;
use App\User;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Lab404\AuthChecker\Models\Login;
use Maatwebsite\Excel\Facades\Excel; use Maatwebsite\Excel\Facades\Excel;
class ManageController extends Controller class ManageController extends Controller
@ -124,4 +126,16 @@ class ManageController extends Controller
$this->flash(__('Settings updated!'), 'success'); $this->flash(__('Settings updated!'), 'success');
return redirect()->back(); return redirect()->back();
} }
public function logins(Request $request)
{
/** @var User $user */
$user = $request->user();
$logins = Login::where('user_id', $user->id)->latest()->paginate(25);
return view('manage.logins')->with([
'logins' => $logins,
'page_title' => __('Logins'),
]);
}
} }

View File

@ -18,7 +18,7 @@ class RedirectIfAuthenticated
public function handle($request, Closure $next, $guard = null) public function handle($request, Closure $next, $guard = null)
{ {
if (Auth::guard($guard)->check()) { if (Auth::guard($guard)->check()) {
return redirect('/home'); return redirect('/');
} }
return $next($request); return $next($request);

View File

@ -5,10 +5,13 @@ namespace App;
use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable; use Illuminate\Notifications\Notifiable;
use Illuminate\Support\Str; use Illuminate\Support\Str;
use Lab404\AuthChecker\Interfaces\HasLoginsAndDevicesInterface;
use Lab404\AuthChecker\Models\HasLoginsAndDevices;
class User extends Authenticatable class User extends Authenticatable implements HasLoginsAndDevicesInterface
{ {
use Notifiable; use Notifiable,
HasLoginsAndDevices;
protected $fillable = [ protected $fillable = [
'name', 'name',

View File

@ -1,3 +1,14 @@
# 1.2.3
## Added
- Log all logins and associated devices with new section in settings
## Fixed
- Typo in secure login page title
- Redirection when already logged
# 1.2.2 # 1.2.2
## Added ## Added

View File

@ -12,16 +12,17 @@
"php": "^7.2", "php": "^7.2",
"doctrine/dbal": "^2.9", "doctrine/dbal": "^2.9",
"fideloper/proxy": "^4.0", "fideloper/proxy": "^4.0",
"hashids/hashids": "^2.0.4|~3.0",
"lab404/laravel-auth-checker": "^1.4",
"laravel/framework": "^6.0", "laravel/framework": "^6.0",
"laravel/scout": "^7.1", "laravel/scout": "^7.1",
"laravel/tinker": "^1.0", "laravel/tinker": "^1.0",
"maatwebsite/excel": "^3.1", "maatwebsite/excel": "^3.1",
"marceauka/laravel-scout-tntsearch-driver": "^7.2",
"nesk/puphpeteer": "^1.6", "nesk/puphpeteer": "^1.6",
"norkunas/youtube-dl-php": "^1.3", "norkunas/youtube-dl-php": "^1.3",
"predis/predis": "^1.1", "predis/predis": "^1.1",
"spatie/valuestore": "^1.2", "spatie/valuestore": "^1.2"
"hashids/hashids": "^2.0.4|~3.0",
"marceauka/laravel-scout-tntsearch-driver": "^7.2"
}, },
"require-dev": { "require-dev": {
"facade/ignition": "^1.4", "facade/ignition": "^1.4",

234
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "d5160bf7c9afca7b33c069cd8edec517", "content-hash": "fedbba17b0cc9c7ab416eaa9f1821a73",
"packages": [ "packages": [
{ {
"name": "clue/socket-raw", "name": "clue/socket-raw",
@ -820,6 +820,186 @@
"description": "Highlight PHP code in terminal", "description": "Highlight PHP code in terminal",
"time": "2018-09-29T18:48:56+00:00" "time": "2018-09-29T18:48:56+00:00"
}, },
{
"name": "jaybizzle/crawler-detect",
"version": "v1.2.84",
"source": {
"type": "git",
"url": "https://github.com/JayBizzle/Crawler-Detect.git",
"reference": "b7f35477a56609dd0d753c07ada912b66af3df01"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/JayBizzle/Crawler-Detect/zipball/b7f35477a56609dd0d753c07ada912b66af3df01",
"reference": "b7f35477a56609dd0d753c07ada912b66af3df01",
"shasum": ""
},
"require": {
"php": ">=5.3.0"
},
"require-dev": {
"phpunit/phpunit": "^4.8|^5.5|^6.5",
"satooshi/php-coveralls": "1.*"
},
"type": "library",
"autoload": {
"psr-4": {
"Jaybizzle\\CrawlerDetect\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Mark Beech",
"email": "m@rkbee.ch",
"role": "Developer"
}
],
"description": "CrawlerDetect is a PHP class for detecting bots/crawlers/spiders via the user agent",
"homepage": "https://github.com/JayBizzle/Crawler-Detect/",
"keywords": [
"crawler",
"crawler detect",
"crawler detector",
"crawlerdetect",
"php crawler detect"
],
"time": "2019-06-14T21:10:21+00:00"
},
{
"name": "jenssegers/agent",
"version": "v2.6.3",
"source": {
"type": "git",
"url": "https://github.com/jenssegers/agent.git",
"reference": "bcb895395e460478e101f41cdab139c48dc721ce"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/jenssegers/agent/zipball/bcb895395e460478e101f41cdab139c48dc721ce",
"reference": "bcb895395e460478e101f41cdab139c48dc721ce",
"shasum": ""
},
"require": {
"jaybizzle/crawler-detect": "^1.2",
"mobiledetect/mobiledetectlib": "^2.7.6",
"php": ">=5.6"
},
"require-dev": {
"php-coveralls/php-coveralls": "^2.1",
"phpunit/phpunit": "^5.0|^6.0|^7.0"
},
"suggest": {
"illuminate/support": "^4.0|^5.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "3.0-dev"
},
"laravel": {
"providers": [
"Jenssegers\\Agent\\AgentServiceProvider"
],
"aliases": {
"Agent": "Jenssegers\\Agent\\Facades\\Agent"
}
}
},
"autoload": {
"psr-4": {
"Jenssegers\\Agent\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Jens Segers",
"homepage": "https://jenssegers.com"
}
],
"description": "Desktop/mobile user agent parser with support for Laravel, based on Mobiledetect",
"homepage": "https://github.com/jenssegers/agent",
"keywords": [
"Agent",
"browser",
"desktop",
"laravel",
"mobile",
"platform",
"user agent",
"useragent"
],
"time": "2019-01-19T21:32:55+00:00"
},
{
"name": "lab404/laravel-auth-checker",
"version": "1.4.2",
"source": {
"type": "git",
"url": "https://github.com/404labfr/laravel-auth-checker.git",
"reference": "2f3cb283fb4fd6cf50234a56c689a6a8fdf483b1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/404labfr/laravel-auth-checker/zipball/2f3cb283fb4fd6cf50234a56c689a6a8fdf483b1",
"reference": "2f3cb283fb4fd6cf50234a56c689a6a8fdf483b1",
"shasum": ""
},
"require": {
"jenssegers/agent": "^2.6",
"laravel/framework": "^6.0",
"php": "^7.2"
},
"require-dev": {
"orchestra/database": "^4.0",
"orchestra/testbench": "^4.0",
"phpunit/phpunit": "^8.0"
},
"type": "library",
"extra": {
"laravel": {
"providers": [
"Lab404\\AuthChecker\\AuthCheckerServiceProvider"
]
}
},
"autoload": {
"psr-4": {
"Lab404\\AuthChecker\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "404lab",
"email": "web@404lab.fr"
}
],
"description": "Laravel Auth Checker allows you to log users authentication, devices authenticated from and lock intrusions.",
"keywords": [
"Authentication",
"IP",
"check",
"device",
"laravel",
"laravel-package",
"laravel-plugin",
"package",
"plugin",
"user"
],
"time": "2019-09-26T13:29:34+00:00"
},
{ {
"name": "laravel/framework", "name": "laravel/framework",
"version": "v6.0.4", "version": "v6.0.4",
@ -1477,6 +1657,58 @@
], ],
"time": "2018-11-04T22:12:12+00:00" "time": "2018-11-04T22:12:12+00:00"
}, },
{
"name": "mobiledetect/mobiledetectlib",
"version": "2.8.34",
"source": {
"type": "git",
"url": "https://github.com/serbanghita/Mobile-Detect.git",
"reference": "6f8113f57a508494ca36acbcfa2dc2d923c7ed5b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/serbanghita/Mobile-Detect/zipball/6f8113f57a508494ca36acbcfa2dc2d923c7ed5b",
"reference": "6f8113f57a508494ca36acbcfa2dc2d923c7ed5b",
"shasum": ""
},
"require": {
"php": ">=5.0.0"
},
"require-dev": {
"phpunit/phpunit": "~4.8.35||~5.7"
},
"type": "library",
"autoload": {
"classmap": [
"Mobile_Detect.php"
],
"psr-0": {
"Detection": "namespaced/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Serban Ghita",
"email": "serbanghita@gmail.com",
"homepage": "http://mobiledetect.net",
"role": "Developer"
}
],
"description": "Mobile_Detect is a lightweight PHP class for detecting mobile devices. It uses the User-Agent string combined with specific HTTP headers to detect the mobile environment.",
"homepage": "https://github.com/serbanghita/Mobile-Detect",
"keywords": [
"detect mobile devices",
"mobile",
"mobile detect",
"mobile detector",
"php mobile detect"
],
"time": "2019-09-18T18:44:20+00:00"
},
{ {
"name": "monolog/monolog", "name": "monolog/monolog",
"version": "2.0.0", "version": "2.0.0",

66
config/auth-checker.php Normal file
View File

@ -0,0 +1,66 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Throttling authentication logs
|--------------------------------------------------------------------------
|
| You can skip authentication logs for a device if the last authentication
| log creation is inferior to the throttle value. Set 0 to disable
| throttling, or set the throttling time in minutes.
|
*/
'throttle' => env('AUTH_CHECKER_THROTTLE', 0),
/*
|--------------------------------------------------------------------------
| Device matching attributes
|--------------------------------------------------------------------------
|
| Declare fields that are used to define if a device is new or not for an
| user. For example, specifying 'platform', 'platform_version' and
| 'browser' will not create a new device if the user already has
| a device registered for these attributes.
|
*/
'device_matching_attributes' => [
# Ex: OS X, Windows, ...
'platform',
# Ex: 10_12_2, 8, ...
'platform_version',
# Ex: Chrome, Firefox, ...
'browser',
# Ex: 42.0.2311.135, 37.0, ...
//'browser_version',
],
/*
|--------------------------------------------------------------------------
| User login column
|--------------------------------------------------------------------------
|
| Declare the name of the column used to authenticate an user.
| By default, it's 'email' but you can change it to your needs.
|
*/
'login_column' => 'email',
/*
|--------------------------------------------------------------------------
| Models
|--------------------------------------------------------------------------
|
| Customize models used by the package.
| Custom models must extends defaults ones.
|
*/
'models' => [
# Ex: App\Models\Device (default: Lab404\AuthChecker\Models\Device)
'device' => null,
# Ex: App\Models\Login (default: Lab404\AuthChecker\Models\Login)
'login' => null,
]
];

View File

@ -0,0 +1,33 @@
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateDevicesTable extends Migration
{
public function up()
{
Schema::create('devices', function (Blueprint $table) {
$table->bigIncrements('id');
$table->bigInteger('user_id')->unsigned()->index();
$table->string('platform')->nullable();
$table->string('platform_version')->nullable();
$table->string('browser')->nullable();
$table->string('browser_version')->nullable();
$table->boolean('is_desktop')->default(0);
$table->boolean('is_mobile')->default(0);
$table->string('language')->nullable();
$table->boolean('is_trusted')->default(0)->index();
$table->boolean('is_untrusted')->default(0)->index();
$table->timestamps();
// $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
});
}
public function down()
{
Schema::dropIfExists('devices');
}
}

View File

@ -0,0 +1,27 @@
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateLoginsTable extends Migration
{
public function up()
{
Schema::create('logins', function (Blueprint $table) {
$table->bigIncrements('id');
$table->ipAddress('ip_address');
$table->string('type')->default(\Lab404\AuthChecker\Models\Login::TYPE_LOGIN)->index();
$table->bigInteger('user_id')->unsigned()->index();
$table->bigInteger('device_id')->unsigned()->index()->nullable();
$table->timestamps();
// $table->foreign('device_id')->references('id')->on('devices')->onDelete('cascade');
});
}
public function down()
{
Schema::dropIfExists('logins');
}
}

View File

@ -61,6 +61,7 @@
"Contents": "Contenus", "Contents": "Contenus",
"Session": "Session", "Session": "Session",
"Account": "Compte", "Account": "Compte",
"Logins": "Connexions",
"Import": "Importer", "Import": "Importer",
"Export": "Exporter", "Export": "Exporter",
"More": "Plus", "More": "Plus",
@ -150,6 +151,15 @@
"Youtube-dl binary": "Éxécutable Youtube-dl", "Youtube-dl binary": "Éxécutable Youtube-dl",
"Settings updated!": "Paramètres mis à jour !", "Settings updated!": "Paramètres mis à jour !",
"No logins": "Aucune connexion",
"IP address": "Adresse IP",
"System": "Système",
"Date": "Date",
"Status": "Statut",
"Succeeded": "Réussie",
"Locked": "Bloquée",
"Failed": "Échouée",
"Update account": "Modifier le compte", "Update account": "Modifier le compte",
"Your account has been updated!": "Votre compte a été mis à jour !", "Your account has been updated!": "Votre compte a été mis à jour !",
"Update password": "Modifier le mot de passe", "Update password": "Modifier le mot de passe",

View File

@ -17,6 +17,9 @@
<a href="{{ route('manage.tags') }}" <a href="{{ route('manage.tags') }}"
class="list-group-item list-group-item-action{{ request()->is('manage/tags') ? ' active' : '' }}" class="list-group-item list-group-item-action{{ request()->is('manage/tags') ? ' active' : '' }}"
>{{ __('Tags') }}</a> >{{ __('Tags') }}</a>
<a href="{{ route('manage.logins') }}"
class="list-group-item list-group-item-action{{ request()->is('manage/logins') ? ' active' : '' }}"
>{{ __('Logins') }}</a>
<a href="{{ route('manage.export') }}" <a href="{{ route('manage.export') }}"
class="list-group-item list-group-item-action{{ request()->is('manage/export') ? ' active' : '' }}" class="list-group-item list-group-item-action{{ request()->is('manage/export') ? ' active' : '' }}"
>{{ __('Export') }}</a> >{{ __('Export') }}</a>

View File

@ -5,7 +5,7 @@
<div class="row justify-content-center"> <div class="row justify-content-center">
<div class="col-md-8"> <div class="col-md-8">
<div class="card"> <div class="card">
<div class="card-header">{{ __('Secure Login') }}</div> <div class="card-header">{{ __('Secure login') }}</div>
<div class="card-body"> <div class="card-body">
<form method="POST" action="{{ route('login.secure', $token) }}"> <form method="POST" action="{{ route('login.secure', $token) }}">

View File

@ -0,0 +1,64 @@
@extends('layouts.manage')
@section('content')
<div class="row justify-content-center">
<div class="col-12">
<div class="card">
<div class="card-header">{{ __('Logins') }}</div>
<div class="card-body">
@if($logins->isEmpty())
<div class="alert alert-info">{{ __('No logins') }}</div>
@else
<div class="table-responsive">
<table class="table table-sm table-striped">
<thead>
<tr>
<th>{{ __('IP address') }}</th>
<th>{{ __('System') }}</th>
<th>{{ __('Date') }}</th>
<th>{{ __('Status') }}</th>
</tr>
</thead>
<tbody>
@foreach($logins as $login)
<tr>
<td class="align-middle">
<a href="https://www.ip-tracker.org/locator/ip-lookup.php?ip={{ $login->ip_address }}" title="TraceIP" target="_blank">
{{ $login->ip_address }}
</a>
</td>
<td>
<div class="d-flex align-items-center">
<div class="mr-2">
{{ $login->device->is_mobile ? '📱' : '🖥' }}
</div>
<div class="d-flex flex-column">
<div>{{ $login->device->platform }} <small>{{ $login->device->platform_version }}</small></div>
<div>{{ $login->device->browser }} <small>{{ $login->device->browser_version }}</small></div>
</div>
</div>
</td>
<td class="align-middle">{{ $login->created_at->diffForHumans() }}</td>
<td class="align-middle">
@if($login->type === \Lab404\AuthChecker\Models\Login::TYPE_LOGIN)
<span class="badge badge-success">{{ __('Succeeded') }}</span>
@elseif($login->type === \Lab404\AuthChecker\Models\Login::TYPE_LOCKOUT)
<span class="badge badge-danger">{{ __('Locked') }}</span>
@else
<span class="badge badge-danger">{{ __('Failed') }}</span>
@endif
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
{{ $logins->links() }}
@endif
</div>
</div>
</div>
</div>
@endsection

View File

@ -2,12 +2,12 @@
<ul class="pagination justify-content-center" role="navigation"> <ul class="pagination justify-content-center" role="navigation">
{{-- Previous Page Link --}} {{-- Previous Page Link --}}
@if ($paginator->onFirstPage()) @if ($paginator->onFirstPage())
<li class="page-item disabled" aria-disabled="true" aria-label="@lang('pagination.previous')"> <li class="page-item disabled" aria-disabled="true" aria-label="previous">
<span class="page-link" aria-hidden="true">&lsaquo;</span> <span class="page-link" aria-hidden="true">&lsaquo;</span>
</li> </li>
@else @else
<li class="page-item"> <li class="page-item">
<a class="page-link" href="{{ $paginator->previousPageUrl() }}" rel="prev" aria-label="@lang('pagination.previous')">&lsaquo;</a> <a class="page-link" href="{{ $paginator->previousPageUrl() }}" rel="prev" aria-label="previous">&lsaquo;</a>
</li> </li>
@endif @endif
@ -33,10 +33,10 @@
{{-- Next Page Link --}} {{-- Next Page Link --}}
@if ($paginator->hasMorePages()) @if ($paginator->hasMorePages())
<li class="page-item"> <li class="page-item">
<a class="page-link" href="{{ $paginator->nextPageUrl() }}" rel="next" aria-label="@lang('pagination.next')">&rsaquo;</a> <a class="page-link" href="{{ $paginator->nextPageUrl() }}" rel="next" aria-label="next">&rsaquo;</a>
</li> </li>
@else @else
<li class="page-item disabled" aria-disabled="true" aria-label="@lang('pagination.next')"> <li class="page-item disabled" aria-disabled="true" aria-label="next">
<span class="page-link" aria-hidden="true">&rsaquo;</span> <span class="page-link" aria-hidden="true">&rsaquo;</span>
</li> </li>
@endif @endif

View File

@ -42,5 +42,6 @@ Route::get('manage/export', 'ManageController@exportForm')->name('manage.export'
Route::post('manage/export', 'ManageController@export'); Route::post('manage/export', 'ManageController@export');
Route::get('manage/tags', 'ManageController@tags')->name('manage.tags'); Route::get('manage/tags', 'ManageController@tags')->name('manage.tags');
Route::get('manage/tags/delete/{tag}/{hash}', 'ManageController@deleteTag')->name('manage.tags.delete'); Route::get('manage/tags/delete/{tag}/{hash}', 'ManageController@deleteTag')->name('manage.tags.delete');
Route::get('manage/logins', 'ManageController@logins')->name('manage.logins');
Route::get('manage/settings', 'ManageController@settingsForm')->name('manage.settings'); Route::get('manage/settings', 'ManageController@settingsForm')->name('manage.settings');
Route::post('manage/settings', 'ManageController@settingsStore'); Route::post('manage/settings', 'ManageController@settingsStore');