Add auto-lock option

This commit is contained in:
Bubka 2020-10-08 15:38:36 +02:00
parent 07df0cd5e0
commit 9b34159c4c
14 changed files with 266 additions and 17 deletions

View File

@ -9,6 +9,7 @@
use Illuminate\Support\Facades\Lang; use Illuminate\Support\Facades\Lang;
use Illuminate\Validation\ValidationException; use Illuminate\Validation\ValidationException;
use Illuminate\Foundation\Auth\AuthenticatesUsers; use Illuminate\Foundation\Auth\AuthenticatesUsers;
use Carbon\Carbon;
class LoginController extends Controller class LoginController extends Controller
@ -73,6 +74,8 @@ protected function sendLoginResponse(Request $request)
$success['token'] = $this->guard()->user()->createToken('2FAuth')->accessToken; $success['token'] = $this->guard()->user()->createToken('2FAuth')->accessToken;
$success['name'] = $this->guard()->user()->name; $success['name'] = $this->guard()->user()->name;
$this->authenticated($request, $this->guard()->user());
return response()->json(['message' => $success], Response::HTTP_OK); return response()->json(['message' => $success], Response::HTTP_OK);
} }
@ -119,6 +122,18 @@ protected function validateLogin(Request $request)
]); ]);
} }
/**
* The user has been authenticated.
*
* @param \Illuminate\Http\Request $request
* @param mixed $user
* @return mixed
*/
protected function authenticated(Request $request, $user)
{
$user->last_seen_at = Carbon::now()->format('Y-m-d H:i:s');
$user->save();
}
/** /**
* log out current user * log out current user

View File

@ -41,6 +41,8 @@ class Kernel extends HttpKernel
'api' => [ 'api' => [
'throttle:60,1', 'throttle:60,1',
'bindings', 'bindings',
\App\Http\Middleware\LogoutInactiveUser::class,
\App\Http\Middleware\LogUserLastSeen::class,
], ],
]; ];

View File

@ -0,0 +1,28 @@
<?php
namespace App\Http\Middleware;
use Closure;
use Carbon\Carbon;
use Illuminate\Support\Facades\Auth;
class LogUserLastSeen
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle($request, Closure $next)
{
if( Auth::guard('api')->check() ) {
Auth::guard('api')->user()->last_seen_at = Carbon::now()->format('Y-m-d H:i:s');
Auth::guard('api')->user()->save();
}
return $next($request);
}
}

View File

@ -0,0 +1,52 @@
<?php
namespace App\Http\Middleware;
use Closure;
use App\User;
use Carbon\Carbon;
use App\Classes\Options;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\Auth;
class LogoutInactiveUser
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle($request, Closure $next)
{
// Not a logged in user
if (!Auth::guard('api')->check()) {
return $next($request);
}
$user = Auth::guard('api')->user();
$now = Carbon::now();
$last_seen = Carbon::parse($user->last_seen_at);
$inactiveFor = $now->diffInMinutes($last_seen);
// Fetch all setting values
$settings = Options::get();
// If user has been inactivity longer than the allowed inactivity period
if ($settings['kickUserAfter'] > 0 && $inactiveFor > $settings['kickUserAfter']) {
$user->last_seen_at = $now->format('Y-m-d H:i:s');
$user->save();
$accessToken = Auth::user()->token();
$accessToken->revoke();
return response()->json(['message' => 'unauthorised'], Response::HTTP_UNAUTHORIZED);
}
return $next($request);
}
}

View File

@ -37,7 +37,8 @@
'closeTokenOnCopy' => false, 'closeTokenOnCopy' => false,
'useBasicQrcodeReader' => false, 'useBasicQrcodeReader' => false,
'displayMode' => 'list', 'displayMode' => 'list',
'showAccountsIcons' => true 'showAccountsIcons' => true,
'kickUserAfter' => '15'
], ],
/* /*
@ -198,7 +199,7 @@
App\Providers\AuthServiceProvider::class, App\Providers\AuthServiceProvider::class,
// App\Providers\BroadcastServiceProvider::class, // App\Providers\BroadcastServiceProvider::class,
App\Providers\EventServiceProvider::class, App\Providers\EventServiceProvider::class,
App\Providers\RouteServiceProvider::class, App\Providers\RouteServiceProvider::class
], ],

View File

@ -0,0 +1,32 @@
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class AddLastSeenToUsersTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('users', function (Blueprint $table) {
$table->timestamp('last_seen_at')->nullable();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('users', function (Blueprint $table) {
$table->dropColumn('last_seen_at');
});
}
}

3
resources/js/app.js vendored
View File

@ -1,4 +1,5 @@
import Vue from 'vue' import Vue from 'vue'
import mixins from './mixins'
import router from './routes' import router from './routes'
import api from './api' import api from './api'
import i18n from './langs/i18n' import i18n from './langs/i18n'
@ -6,7 +7,6 @@ import FontAwesome from './packages/fontawesome'
import Clipboard from './packages/clipboard' import Clipboard from './packages/clipboard'
import QrcodeReader from './packages/qrcodeReader' import QrcodeReader from './packages/qrcodeReader'
import Notifications from 'vue-notification' import Notifications from 'vue-notification'
import App from './components/App'
import './components' import './components'
@ -17,7 +17,6 @@ const app = new Vue({
data: { data: {
appSettings: window.appSettings appSettings: window.appSettings
}, },
components: { App },
i18n, i18n,
router, router,
}); });

View File

@ -1,5 +1,6 @@
<template> <template>
<div> <div>
<kicker v-if="kickInactiveUser"></kicker>
<div v-if="$root.appSettings.isDemoApp" class="demo has-background-warning has-text-centered is-size-7-mobile"> <div v-if="$root.appSettings.isDemoApp" class="demo has-background-warning has-text-centered is-size-7-mobile">
{{ $t('commons.demo_do_not_post_sensitive_data') }} {{ $t('commons.demo_do_not_post_sensitive_data') }}
</div> </div>
@ -17,6 +18,14 @@
data(){ data(){
return { return {
} }
},
computed: {
kickInactiveUser: function () {
return parseInt(this.$root.appSettings.kickUserAfter) > 0 && this.$route.meta.requiresAuth
}
} }
} }
</script> </script>

View File

@ -0,0 +1,58 @@
<template>
</template>
<script>
export default {
name: 'Kicker',
data: function () {
return {
events: ['click', 'mousedown', 'scroll', 'keypress', 'load'],
logoutTimer: null
}
},
mounted() {
this.events.forEach(function (event) {
window.addEventListener(event, this.resetTimer)
}, this);
this.setTimer()
},
destroyed() {
this.events.forEach(function (event) {
window.removeEventListener(event, this.resetTimer)
}, this);
clearTimeout(this.logoutTimer)
},
methods: {
setTimer: function() {
this.logoutTimer = setTimeout(this.logoutUser, this.$root.appSettings.kickUserAfter * 60 * 1000)
},
logoutUser: function() {
clearTimeout(this.logoutTimer)
this.appLogout
},
resetTimer: function() {
clearTimeout(this.logoutTimer)
this.setTimer()
}
}
}
</script>

View File

@ -162,9 +162,11 @@
}, },
clipboardSuccessHandler ({ value, event }) { clipboardSuccessHandler ({ value, event }) {
console.log('success', value)
if(this.$root.appSettings.closeTokenOnCopy) { if(this.$root.appSettings.kickUserAfter == -1) {
this.appLogout()
}
else if(this.$root.appSettings.closeTokenOnCopy) {
this.$parent.isActive = false this.$parent.isActive = false
this.clearOTP() this.clearOTP()
} }

View File

@ -1,16 +1,19 @@
import Vue from 'vue' import Vue from 'vue'
import Button from './Button' import App from './App'
import FieldError from './FieldError' import Button from './Button'
import FormWrapper from './FormWrapper' import FieldError from './FieldError'
import FormField from './FormField' import FormWrapper from './FormWrapper'
import FormSelect from './FormSelect' import FormField from './FormField'
import FormSwitch from './FormSwitch' import FormSelect from './FormSelect'
import FormSwitch from './FormSwitch'
import FormCheckbox from './FormCheckbox' import FormCheckbox from './FormCheckbox'
import FormButtons from './FormButtons' import FormButtons from './FormButtons'
import VueFooter from './Footer' import VueFooter from './Footer'
import Kicker from './Kicker'
// Components that are registered globaly. // Components that are registered globaly.
[ [
App,
Button, Button,
FieldError, FieldError,
FormWrapper, FormWrapper,
@ -20,6 +23,7 @@ import VueFooter from './Footer'
FormCheckbox, FormCheckbox,
FormButtons, FormButtons,
VueFooter, VueFooter,
Kicker
].forEach(Component => { ].forEach(Component => {
Vue.component(Component.name, Component) Vue.component(Component.name, Component)
}) })

View File

@ -1,12 +1,16 @@
<template> <template>
<form-wrapper> <form-wrapper>
<form @submit.prevent="handleSubmit" @change="handleSubmit" @keydown="form.onKeydown($event)"> <form @submit.prevent="handleSubmit" @change="handleSubmit" @keydown="form.onKeydown($event)">
<h4 class="title is-4">{{ $t('settings.general') }}</h4>
<form-select :options="langs" :form="form" fieldName="lang" :label="$t('settings.forms.language.label')" :help="$t('settings.forms.language.help')" /> <form-select :options="langs" :form="form" fieldName="lang" :label="$t('settings.forms.language.label')" :help="$t('settings.forms.language.help')" />
<form-select :options="layouts" :form="form" fieldName="displayMode" :label="$t('settings.forms.display_mode.label')" :help="$t('settings.forms.display_mode.help')" /> <form-select :options="layouts" :form="form" fieldName="displayMode" :label="$t('settings.forms.display_mode.label')" :help="$t('settings.forms.display_mode.help')" />
<form-checkbox :form="form" fieldName="showAccountsIcons" :label="$t('settings.forms.show_accounts_icons.label')" :help="$t('settings.forms.show_accounts_icons.help')" />
<h4 class="title is-4">{{ $t('settings.security') }}</h4>
<form-select :options="kickUserAfters" :form="form" fieldName="kickUserAfter" :label="$t('settings.forms.auto_lock.label')" :help="$t('settings.forms.auto_lock.help')" />
<form-checkbox :form="form" fieldName="showTokenAsDot" :label="$t('settings.forms.show_token_as_dot.label')" :help="$t('settings.forms.show_token_as_dot.help')" /> <form-checkbox :form="form" fieldName="showTokenAsDot" :label="$t('settings.forms.show_token_as_dot.label')" :help="$t('settings.forms.show_token_as_dot.help')" />
<form-checkbox :form="form" fieldName="closeTokenOnCopy" :label="$t('settings.forms.close_token_on_copy.label')" :help="$t('settings.forms.close_token_on_copy.help')" /> <form-checkbox :form="form" fieldName="closeTokenOnCopy" :label="$t('settings.forms.close_token_on_copy.label')" :help="$t('settings.forms.close_token_on_copy.help')" />
<h4 class="title is-4">{{ $t('settings.advanced') }}</h4>
<form-checkbox :form="form" fieldName="useBasicQrcodeReader" :label="$t('settings.forms.use_basic_qrcode_reader.label')" :help="$t('settings.forms.use_basic_qrcode_reader.help')" /> <form-checkbox :form="form" fieldName="useBasicQrcodeReader" :label="$t('settings.forms.use_basic_qrcode_reader.label')" :help="$t('settings.forms.use_basic_qrcode_reader.help')" />
<form-checkbox :form="form" fieldName="showAccountsIcons" :label="$t('settings.forms.show_accounts_icons.label')" :help="$t('settings.forms.show_accounts_icons.help')" />
</form> </form>
</form-wrapper> </form-wrapper>
</template> </template>
@ -25,6 +29,7 @@
useBasicQrcodeReader: this.$root.appSettings.useBasicQrcodeReader, useBasicQrcodeReader: this.$root.appSettings.useBasicQrcodeReader,
showAccountsIcons: this.$root.appSettings.showAccountsIcons, showAccountsIcons: this.$root.appSettings.showAccountsIcons,
displayMode: this.$root.appSettings.displayMode, displayMode: this.$root.appSettings.displayMode,
kickUserAfter: this.$root.appSettings.kickUserAfter,
}), }),
langs: [ langs: [
{ text: this.$t('languages.en'), value: 'en' }, { text: this.$t('languages.en'), value: 'en' },
@ -33,6 +38,17 @@
layouts: [ layouts: [
{ text: this.$t('settings.forms.grid'), value: 'grid' }, { text: this.$t('settings.forms.grid'), value: 'grid' },
{ text: this.$t('settings.forms.list'), value: 'list' }, { text: this.$t('settings.forms.list'), value: 'list' },
],
kickUserAfters: [
{ text: this.$t('settings.forms.never'), value: '0' },
{ text: this.$t('settings.forms.on_token_copy'), value: '-1' },
{ text: this.$t('settings.forms.1_minutes'), value: '1' },
{ text: this.$t('settings.forms.5_minutes'), value: '5' },
{ text: this.$t('settings.forms.10_minutes'), value: '10' },
{ text: this.$t('settings.forms.15_minutes'), value: '15' },
{ text: this.$t('settings.forms.30_minutes'), value: '30' },
{ text: this.$t('settings.forms.1_hour'), value: '60' },
{ text: this.$t('settings.forms.1_day'), value: '1440' },
] ]
} }
}, },

View File

@ -20,6 +20,9 @@
'confirm' => [ 'confirm' => [
], ],
'general' => 'General',
'security' => 'Security',
'advanced' => 'Advanced',
'forms' => [ 'forms' => [
'edit_settings' => 'Edit settings', 'edit_settings' => 'Edit settings',
'setting_saved' => 'Settings saved', 'setting_saved' => 'Settings saved',
@ -49,6 +52,19 @@
'label' => 'Show icons', 'label' => 'Show icons',
'help' => 'Show icons accounts in the main view' 'help' => 'Show icons accounts in the main view'
], ],
'auto_lock' => [
'label' => 'Auto lock',
'help' => 'Log out the user automatically in case of inactivity'
],
'never' => 'Never',
'on_token_copy' => 'On security code copy',
'1_minutes' => 'After 1 minute',
'5_minutes' => 'After 5 minutes',
'10_minutes' => 'After 10 minutes',
'15_minutes' => 'After 15 minutes',
'30_minutes' => 'After 15 minutes',
'1_hour' => 'After 1 hour',
'1_day' => 'After 1 day',
], ],

View File

@ -20,6 +20,9 @@
'confirm' => [ 'confirm' => [
], ],
'general' => 'General',
'security' => 'Sécurité',
'advanced' => 'Avancés',
'forms' => [ 'forms' => [
'edit_settings' => 'Modifier les réglages', 'edit_settings' => 'Modifier les réglages',
'setting_saved' => 'Réglages sauvegardés', 'setting_saved' => 'Réglages sauvegardés',
@ -49,7 +52,19 @@
'label' => 'Afficher les icônes', 'label' => 'Afficher les icônes',
'help' => 'Affiche les icônes des comptes dans la vue principale' 'help' => 'Affiche les icônes des comptes dans la vue principale'
], ],
'auto_lock' => [
'label' => 'Verouillage automatique',
'help' => 'Déconnecter automatiquement l\'utilisateur en cas d\'inactivité'
],
'never' => 'Jamais',
'on_token_copy' => 'Après copie d\'un code de sécurité',
'1_minutes' => 'Après 1 minute',
'5_minutes' => 'Après 5 minutes',
'10_minutes' => 'Après 10 minutes',
'15_minutes' => 'Après 15 minutes',
'30_minutes' => 'Après 15 minutes',
'1_hour' => 'Après 1 heure',
'1_day' => 'Après 1 journée',
], ],