瀏覽代碼

Add user preference to auto-close OTP after timeout

Bubka 1 年之前
父節點
當前提交
570f3bb9bd

+ 1 - 0
config/2fauth.php

@@ -126,6 +126,7 @@ return [
         'notifyOnFailedLogin' => false,
         'timezone' => env('APP_TIMEZONE', 'UTC'),
         'sortCaseSensitive' => false,
+        'autoCloseTimeout' => 2,
     ],
 
 ];

+ 27 - 3
resources/js/components/OtpDisplay.vue

@@ -53,6 +53,7 @@
     const dots = ref()
     const totpLooper = ref()
     const otpSpanTag = ref()
+    const autoCloseTimeout = ref(null)
 
     watch(
         () => props.icon,
@@ -121,6 +122,10 @@
         try {
             await getOtp()
             focusOnOTP()
+
+            if (user.preferences.getOtpOnRequest && parseInt(user.preferences.autoCloseTimeout) > 0) {
+                startAutoCloseTimer()
+            }
         }
         catch(error) {
             clearOTP()
@@ -191,6 +196,15 @@
         }
     }
 
+    /**
+     * Triggers the component closing
+     */
+    function closeMe() {
+        emit("please-close-me");
+        revealPassword.value = false
+        clearOTP()
+    }
+
     /**
      * Reset component's refs
      */
@@ -199,6 +213,7 @@
         otpauthParams.value.service = otpauthParams.value.account = otpauthParams.value.icon = otpauthParams.value.otp_type = otpauthParams.value.secret = ''
         password.value = '... ...'
         hasTOTP.value = false
+        clearTimeout(autoCloseTimeout.value)
 
         totpLooper.value?.clearLooper();
     }
@@ -226,9 +241,7 @@
                 user.logout({ kicked: true})
             }
             else if(user.preferences.closeOtpOnCopy && (permit_closing || false) === true) {
-                emit("please-close-me");
-                revealPassword.value = false
-                clearOTP()
+                closeMe()
             }
 
             if(user.preferences.clearSearchOnCopy) {
@@ -273,6 +286,17 @@
         show,
         clearOTP
     })
+    
+    /**
+     * Starts an auto close timer
+     */
+    function startAutoCloseTimer() {
+        let duration = parseInt(user.preferences.autoCloseTimeout) // in minutes
+        
+        autoCloseTimeout.value = setTimeout(function() {
+            closeMe()
+        }, duration * 60 * 1000);
+    }
 
 </script>
 

+ 16 - 9
resources/js/components/formElements/FormSelect.vue

@@ -19,22 +19,29 @@
             type: String,
             default: ''
         },
+        isIndented: Boolean,
+        isDisabled: Boolean,
     })
 
     const selected = ref(props.modelValue)
 </script>
 
 <template>
-    <div class="field">
-        <label class="label" v-html="$t(label)"></label>
-        <div class="control">
-            <div class="select">
-                <select v-model="selected" v-on:change="$emit('update:modelValue', $event.target.value)">
-                    <option v-for="option in options" :value="option.value">{{ $t(option.text) }}</option>
-                </select>
+    <div class="field is-flex">
+        <div v-if="isIndented" class="mx-2 pr-1" :style="{ 'opacity': isDisabled ? '0.5' : '1' }">
+            <FontAwesomeIcon class="has-text-grey" :icon="['fas', 'chevron-right']" transform="rotate-135"/>
+        </div>
+        <div>
+            <label class="label" v-html="$t(label)" :style="{ 'opacity': isDisabled ? '0.5' : '1' }"></label>
+            <div class="control">
+                <div class="select">
+                    <select v-model="selected" v-on:change="$emit('update:modelValue', $event.target.value)" :disabled="isDisabled">
+                        <option v-for="option in options" :value="option.value">{{ $t(option.text) }}</option>
+                    </select>
+                </div>
             </div>
+            <FieldError v-if="fieldError != undefined" :error="fieldError" :field="fieldName" />
+            <p class="help" v-html="$t(help)" v-if="help"></p>
         </div>
-        <FieldError v-if="fieldError != undefined" :error="fieldError" :field="fieldName" />
-        <p class="help" v-html="$t(help)" v-if="help"></p>
     </div>
 </template>

+ 8 - 0
resources/js/views/settings/Options.vue

@@ -37,6 +37,12 @@
         { text: 'settings.forms.1_hour', value: 60 },
         { text: 'settings.forms.1_day', value: 1440 }, 
     ]
+    const autoCloseTimeout = [
+        { text: 'settings.forms.never', value: 0 },
+        { text: 'settings.forms.1_minutes', value: 1 },
+        { text: 'settings.forms.2_minutes', value: 2 },
+        { text: 'settings.forms.5_minutes', value: 5 },
+    ]
     const groupsList = ref([
         { text: 'groups.no_group', value: 0 },
         { text: 'groups.active_group', value: -1 },
@@ -157,6 +163,8 @@
                         <FormToggle v-model="user.preferences.getOtpOnRequest" @update:model-value="val => savePreference('getOtpOnRequest', val)" :choices="getOtpTriggers" fieldName="getOtpOnRequest" label="settings.forms.otp_generation.label" help="settings.forms.otp_generation.help"/>
                             <!-- close otp on copy -->
                             <FormCheckbox v-model="user.preferences.closeOtpOnCopy" @update:model-value="val => savePreference('closeOtpOnCopy', val)" fieldName="closeOtpOnCopy" label="settings.forms.close_otp_on_copy.label" help="settings.forms.close_otp_on_copy.help" :isDisabled="!user.preferences.getOtpOnRequest" :isIndented="true" />
+                            <!-- auto-close timeout -->
+                            <FormSelect v-model="user.preferences.autoCloseTimeout" @update:model-value="val => savePreference('autoCloseTimeout', val)" :options="autoCloseTimeout" fieldName="autoCloseTimeout" label="settings.forms.auto_close_timeout.label" help="settings.forms.auto_close_timeout.help"  :isDisabled="!user.preferences.getOtpOnRequest" :isIndented="true" />
                             <!-- clear search on copy -->
                             <FormCheckbox v-model="user.preferences.copyOtpOnDisplay" @update:model-value="val => savePreference('copyOtpOnDisplay', val)" fieldName="copyOtpOnDisplay" label="settings.forms.copy_otp_on_display.label" help="settings.forms.copy_otp_on_display.help" :isDisabled="!user.preferences.getOtpOnRequest" :isIndented="true" />
                         <!-- otp as dot -->

+ 6 - 1
resources/lang/en/settings.php

@@ -67,7 +67,11 @@ return [
         ],
         'close_otp_on_copy' => [
             'label' => 'Close <abbr title="One-Time Password">OTP</abbr> after copy',
-            'help' => 'Clicking a generated password to copy it automatically hide it from the screen'
+            'help' => 'Click on a generated password to copy it automatically hides it from the screen'
+        ],
+        'auto_close_timeout' => [
+            'label' => 'Auto close <abbr title="One-Time Password">OTP</abbr>',
+            'help' => 'Automatically hide on-screen password after a timeout. This avoids unnecessary requests for fresh passwords if you forget to close the password view.'
         ],
         'clear_search_on_copy' => [
             'label' => 'Clear Search on copy',
@@ -161,6 +165,7 @@ return [
         'never' => 'Never',
         'on_otp_copy' => 'On security code copy',
         '1_minutes' => 'After 1 minute',
+        '2_minutes' => 'After 2 minutes',
         '5_minutes' => 'After 5 minutes',
         '10_minutes' => 'After 10 minutes',
         '15_minutes' => 'After 15 minutes',