Add user preference to auto-close OTP after timeout

This commit is contained in:
Bubka 2024-06-21 14:23:03 +02:00
parent 2f05f4993c
commit 570f3bb9bd
5 changed files with 59 additions and 14 deletions

View File

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

View File

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

View File

@ -19,17 +19,23 @@
type: String, type: String,
default: '' default: ''
}, },
isIndented: Boolean,
isDisabled: Boolean,
}) })
const selected = ref(props.modelValue) const selected = ref(props.modelValue)
</script> </script>
<template> <template>
<div class="field"> <div class="field is-flex">
<label class="label" v-html="$t(label)"></label> <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="control">
<div class="select"> <div class="select">
<select v-model="selected" v-on:change="$emit('update:modelValue', $event.target.value)"> <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> <option v-for="option in options" :value="option.value">{{ $t(option.text) }}</option>
</select> </select>
</div> </div>
@ -37,4 +43,5 @@
<FieldError v-if="fieldError != undefined" :error="fieldError" :field="fieldName" /> <FieldError v-if="fieldError != undefined" :error="fieldError" :field="fieldName" />
<p class="help" v-html="$t(help)" v-if="help"></p> <p class="help" v-html="$t(help)" v-if="help"></p>
</div> </div>
</div>
</template> </template>

View File

@ -37,6 +37,12 @@
{ text: 'settings.forms.1_hour', value: 60 }, { text: 'settings.forms.1_hour', value: 60 },
{ text: 'settings.forms.1_day', value: 1440 }, { 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([ const groupsList = ref([
{ text: 'groups.no_group', value: 0 }, { text: 'groups.no_group', value: 0 },
{ text: 'groups.active_group', value: -1 }, { 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"/> <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 --> <!-- 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" /> <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 --> <!-- 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" /> <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 --> <!-- otp as dot -->

View File

@ -67,7 +67,11 @@
], ],
'close_otp_on_copy' => [ 'close_otp_on_copy' => [
'label' => 'Close <abbr title="One-Time Password">OTP</abbr> after 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' => [ 'clear_search_on_copy' => [
'label' => 'Clear Search on copy', 'label' => 'Clear Search on copy',
@ -161,6 +165,7 @@
'never' => 'Never', 'never' => 'Never',
'on_otp_copy' => 'On security code copy', 'on_otp_copy' => 'On security code copy',
'1_minutes' => 'After 1 minute', '1_minutes' => 'After 1 minute',
'2_minutes' => 'After 2 minutes',
'5_minutes' => 'After 5 minutes', '5_minutes' => 'After 5 minutes',
'10_minutes' => 'After 10 minutes', '10_minutes' => 'After 10 minutes',
'15_minutes' => 'After 15 minutes', '15_minutes' => 'After 15 minutes',