mirror of
https://github.com/Bubka/2FAuth.git
synced 2024-11-22 00:03:09 +01:00
Add auto-lock option
This commit is contained in:
parent
07df0cd5e0
commit
9b34159c4c
@ -9,6 +9,7 @@
|
||||
use Illuminate\Support\Facades\Lang;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use Illuminate\Foundation\Auth\AuthenticatesUsers;
|
||||
use Carbon\Carbon;
|
||||
|
||||
|
||||
class LoginController extends Controller
|
||||
@ -73,6 +74,8 @@ protected function sendLoginResponse(Request $request)
|
||||
$success['token'] = $this->guard()->user()->createToken('2FAuth')->accessToken;
|
||||
$success['name'] = $this->guard()->user()->name;
|
||||
|
||||
$this->authenticated($request, $this->guard()->user());
|
||||
|
||||
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
|
||||
|
@ -41,6 +41,8 @@ class Kernel extends HttpKernel
|
||||
'api' => [
|
||||
'throttle:60,1',
|
||||
'bindings',
|
||||
\App\Http\Middleware\LogoutInactiveUser::class,
|
||||
\App\Http\Middleware\LogUserLastSeen::class,
|
||||
],
|
||||
];
|
||||
|
||||
|
28
app/Http/Middleware/LogUserLastSeen.php
Normal file
28
app/Http/Middleware/LogUserLastSeen.php
Normal 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);
|
||||
}
|
||||
}
|
52
app/Http/Middleware/LogoutInactiveUser.php
Normal file
52
app/Http/Middleware/LogoutInactiveUser.php
Normal 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);
|
||||
}
|
||||
}
|
@ -37,7 +37,8 @@
|
||||
'closeTokenOnCopy' => false,
|
||||
'useBasicQrcodeReader' => false,
|
||||
'displayMode' => 'list',
|
||||
'showAccountsIcons' => true
|
||||
'showAccountsIcons' => true,
|
||||
'kickUserAfter' => '15'
|
||||
],
|
||||
|
||||
/*
|
||||
@ -198,7 +199,7 @@
|
||||
App\Providers\AuthServiceProvider::class,
|
||||
// App\Providers\BroadcastServiceProvider::class,
|
||||
App\Providers\EventServiceProvider::class,
|
||||
App\Providers\RouteServiceProvider::class,
|
||||
App\Providers\RouteServiceProvider::class
|
||||
|
||||
],
|
||||
|
||||
|
@ -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
3
resources/js/app.js
vendored
@ -1,4 +1,5 @@
|
||||
import Vue from 'vue'
|
||||
import mixins from './mixins'
|
||||
import router from './routes'
|
||||
import api from './api'
|
||||
import i18n from './langs/i18n'
|
||||
@ -6,7 +7,6 @@ import FontAwesome from './packages/fontawesome'
|
||||
import Clipboard from './packages/clipboard'
|
||||
import QrcodeReader from './packages/qrcodeReader'
|
||||
import Notifications from 'vue-notification'
|
||||
import App from './components/App'
|
||||
|
||||
import './components'
|
||||
|
||||
@ -17,7 +17,6 @@ const app = new Vue({
|
||||
data: {
|
||||
appSettings: window.appSettings
|
||||
},
|
||||
components: { App },
|
||||
i18n,
|
||||
router,
|
||||
});
|
@ -1,5 +1,6 @@
|
||||
<template>
|
||||
<div>
|
||||
<kicker v-if="kickInactiveUser"></kicker>
|
||||
<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') }}
|
||||
</div>
|
||||
@ -17,6 +18,14 @@
|
||||
data(){
|
||||
return {
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
|
||||
kickInactiveUser: function () {
|
||||
return parseInt(this.$root.appSettings.kickUserAfter) > 0 && this.$route.meta.requiresAuth
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
58
resources/js/components/Kicker.vue
Normal file
58
resources/js/components/Kicker.vue
Normal 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>
|
@ -162,9 +162,11 @@
|
||||
},
|
||||
|
||||
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.clearOTP()
|
||||
}
|
||||
|
22
resources/js/components/index.js
vendored
22
resources/js/components/index.js
vendored
@ -1,16 +1,19 @@
|
||||
import Vue from 'vue'
|
||||
import Button from './Button'
|
||||
import FieldError from './FieldError'
|
||||
import FormWrapper from './FormWrapper'
|
||||
import FormField from './FormField'
|
||||
import FormSelect from './FormSelect'
|
||||
import FormSwitch from './FormSwitch'
|
||||
import Vue from 'vue'
|
||||
import App from './App'
|
||||
import Button from './Button'
|
||||
import FieldError from './FieldError'
|
||||
import FormWrapper from './FormWrapper'
|
||||
import FormField from './FormField'
|
||||
import FormSelect from './FormSelect'
|
||||
import FormSwitch from './FormSwitch'
|
||||
import FormCheckbox from './FormCheckbox'
|
||||
import FormButtons from './FormButtons'
|
||||
import VueFooter from './Footer'
|
||||
import FormButtons from './FormButtons'
|
||||
import VueFooter from './Footer'
|
||||
import Kicker from './Kicker'
|
||||
|
||||
// Components that are registered globaly.
|
||||
[
|
||||
App,
|
||||
Button,
|
||||
FieldError,
|
||||
FormWrapper,
|
||||
@ -20,6 +23,7 @@ import VueFooter from './Footer'
|
||||
FormCheckbox,
|
||||
FormButtons,
|
||||
VueFooter,
|
||||
Kicker
|
||||
].forEach(Component => {
|
||||
Vue.component(Component.name, Component)
|
||||
})
|
||||
|
@ -1,12 +1,16 @@
|
||||
<template>
|
||||
<form-wrapper>
|
||||
<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="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="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="showAccountsIcons" :label="$t('settings.forms.show_accounts_icons.label')" :help="$t('settings.forms.show_accounts_icons.help')" />
|
||||
</form>
|
||||
</form-wrapper>
|
||||
</template>
|
||||
@ -25,6 +29,7 @@
|
||||
useBasicQrcodeReader: this.$root.appSettings.useBasicQrcodeReader,
|
||||
showAccountsIcons: this.$root.appSettings.showAccountsIcons,
|
||||
displayMode: this.$root.appSettings.displayMode,
|
||||
kickUserAfter: this.$root.appSettings.kickUserAfter,
|
||||
}),
|
||||
langs: [
|
||||
{ text: this.$t('languages.en'), value: 'en' },
|
||||
@ -33,6 +38,17 @@
|
||||
layouts: [
|
||||
{ text: this.$t('settings.forms.grid'), value: 'grid' },
|
||||
{ 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' },
|
||||
]
|
||||
}
|
||||
},
|
||||
|
@ -20,6 +20,9 @@
|
||||
'confirm' => [
|
||||
|
||||
],
|
||||
'general' => 'General',
|
||||
'security' => 'Security',
|
||||
'advanced' => 'Advanced',
|
||||
'forms' => [
|
||||
'edit_settings' => 'Edit settings',
|
||||
'setting_saved' => 'Settings saved',
|
||||
@ -49,6 +52,19 @@
|
||||
'label' => 'Show icons',
|
||||
'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',
|
||||
],
|
||||
|
||||
|
||||
|
@ -20,6 +20,9 @@
|
||||
'confirm' => [
|
||||
|
||||
],
|
||||
'general' => 'General',
|
||||
'security' => 'Sécurité',
|
||||
'advanced' => 'Avancés',
|
||||
'forms' => [
|
||||
'edit_settings' => 'Modifier les réglages',
|
||||
'setting_saved' => 'Réglages sauvegardés',
|
||||
@ -49,7 +52,19 @@
|
||||
'label' => 'Afficher les icônes',
|
||||
'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',
|
||||
],
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user