Browse Source

Add auto-lock option

Bubka 4 years ago
parent
commit
9b34159c4c

+ 15 - 0
app/Http/Controllers/Auth/LoginController.php

@@ -9,6 +9,7 @@ use Illuminate\Support\Facades\Auth;
 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 @@ class LoginController extends Controller
         $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 @@ class LoginController extends Controller
         ]);
     }
 
+    /**
+     * 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

+ 2 - 0
app/Http/Kernel.php

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

+ 28 - 0
app/Http/Middleware/LogUserLastSeen.php

@@ -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 - 0
app/Http/Middleware/LogoutInactiveUser.php

@@ -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);
+    }
+}

+ 3 - 2
config/app.php

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

+ 32 - 0
database/migrations/2020_10_05_210557_add_last_seen_to_users_table.php

@@ -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');
+        });
+    }
+}

+ 1 - 2
resources/js/app.js

@@ -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,
 });

+ 9 - 0
resources/js/components/App.vue

@@ -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 - 0
resources/js/components/Kicker.vue

@@ -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>

+ 4 - 2
resources/js/components/TwofaccountShow.vue

@@ -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()
                 }

+ 13 - 9
resources/js/components/index.js

@@ -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)
 })

+ 17 - 1
resources/js/views/settings/Options.vue

@@ -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' }, 
                 ]
             }
         },

+ 16 - 0
resources/lang/en/settings.php

@@ -20,6 +20,9 @@ return [
     'confirm' => [
 
     ],
+    'general' => 'General',
+    'security' => 'Security',
+    'advanced' => 'Advanced',
     'forms' => [
         'edit_settings' => 'Edit settings',
         'setting_saved' => 'Settings saved',
@@ -49,6 +52,19 @@ return [
             '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',
     ],
     
 

+ 16 - 1
resources/lang/fr/settings.php

@@ -20,6 +20,9 @@ return [
     '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 @@ return [
             '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',
     ],