Explorar o código

Add a user pref to reveal dotted passwords - Closes #208

Bubka hai 1 ano
pai
achega
b2e733eee5

+ 1 - 0
config/2fauth.php

@@ -83,6 +83,7 @@ return [
 
 
     'preferences' => [
     'preferences' => [
         'showOtpAsDot' => false,
         'showOtpAsDot' => false,
+        'revealDottedOTP' => false,
         'closeOtpOnCopy' => false,
         'closeOtpOnCopy' => false,
         'copyOtpOnDisplay' => false,
         'copyOtpOnDisplay' => false,
         'useBasicQrcodeReader' => false,
         'useBasicQrcodeReader' => false,

+ 13 - 4
resources/js/components/OtpDisplay.vue

@@ -48,6 +48,7 @@
     const generated_at = ref(null)
     const generated_at = ref(null)
     const hasTOTP = ref(false)
     const hasTOTP = ref(false)
     const showInlineSpinner = ref(false)
     const showInlineSpinner = ref(false)
+    const revealPassword = ref(false)
 
 
     const dots = ref()
     const dots = ref()
     const totpLooper = ref()
     const totpLooper = ref()
@@ -66,6 +67,7 @@
      * 
      * 
      */
      */
     const show = async (accountId) => {
     const show = async (accountId) => {
+        revealPassword.value = false
 
 
         // 3 possible cases :
         // 3 possible cases :
         //
         //
@@ -225,6 +227,7 @@
             }
             }
             else if(user.preferences.closeOtpOnCopy && (permit_closing || false) === true) {
             else if(user.preferences.closeOtpOnCopy && (permit_closing || false) === true) {
                 emit("please-close-me");
                 emit("please-close-me");
+                revealPassword.value = false
                 clearOTP()
                 clearOTP()
             }
             }
 
 
@@ -285,7 +288,7 @@
                     @keyup.enter="copyOTP(password, true)"
                     @keyup.enter="copyOTP(password, true)"
                     :title="$t('commons.copy_to_clipboard')"
                     :title="$t('commons.copy_to_clipboard')"
                 >
                 >
-                    {{ useDisplayablePassword(password) }}
+                    {{ useDisplayablePassword(password, user.preferences.showOtpAsDot && user.preferences.revealDottedOTP && revealPassword) }}
                 </span>
                 </span>
                 <span v-else tabindex="0" class="otp is-size-1">
                 <span v-else tabindex="0" class="otp is-size-1">
                     <Spinner :isVisible="showInlineSpinner" :type="'raw'" />
                     <Spinner :isVisible="showInlineSpinner" :type="'raw'" />
@@ -293,9 +296,15 @@
             </p>
             </p>
         </UseColorMode>
         </UseColorMode>
         <Dots v-show="isTimeBased(otpauthParams.otp_type)" ref="dots"></Dots>
         <Dots v-show="isTimeBased(otpauthParams.otp_type)" ref="dots"></Dots>
-        <ul v-show="isHMacBased(otpauthParams.otp_type)">
-            <li>counter: {{ otpauthParams.counter }}</li>
-        </ul>
+        <p v-show="isHMacBased(otpauthParams.otp_type)">
+            {{ $t('twofaccounts.forms.counter.label') }}: {{ otpauthParams.counter }}
+        </p>
+        <p v-if="user.preferences.showOtpAsDot && user.preferences.revealDottedOTP" class="mt-3">
+            <button class="button is-ghost has-text-grey-dark" @click.stop="revealPassword = !revealPassword">
+                <font-awesome-icon v-if="revealPassword" :icon="['fas', 'eye']" />
+                <font-awesome-icon v-else :icon="['fas', 'eye-slash']" />
+            </button>
+        </p>
         <TotpLooper 
         <TotpLooper 
             v-if="hasTOTP"
             v-if="hasTOTP"
             :period="otpauthParams.period" 
             :period="otpauthParams.period" 

+ 2 - 2
resources/js/composables/helpers.js

@@ -34,7 +34,7 @@ export function useIdGenerator(fieldType, fieldName) {
 	}
 	}
 }
 }
 
 
-export function useDisplayablePassword(pwd) {
+export function useDisplayablePassword(pwd, reveal = false) {
     const user = useUserStore()
     const user = useUserStore()
 
 
 	if (user.preferences.formatPassword && pwd.length > 0) {
 	if (user.preferences.formatPassword && pwd.length > 0) {
@@ -48,5 +48,5 @@ export function useDisplayablePassword(pwd) {
 		}
 		}
 	}
 	}
 
 
-	return user.preferences.showOtpAsDot ? pwd.replace(/[0-9]/g, '●') : pwd
+	return user.preferences.showOtpAsDot && !reveal ? pwd.replace(/[0-9]/g, '●') : pwd
 }
 }

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

@@ -165,6 +165,8 @@
                             <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 -->
                         <FormCheckbox v-model="user.preferences.showOtpAsDot" @update:model-value="val => savePreference('showOtpAsDot', val)" fieldName="showOtpAsDot" label="settings.forms.show_otp_as_dot.label" help="settings.forms.show_otp_as_dot.help" />
                         <FormCheckbox v-model="user.preferences.showOtpAsDot" @update:model-value="val => savePreference('showOtpAsDot', val)" fieldName="showOtpAsDot" label="settings.forms.show_otp_as_dot.label" help="settings.forms.show_otp_as_dot.help" />
+                            <!-- reveal dotted OTPs -->
+                            <FormCheckbox v-model="user.preferences.revealDottedOTP" @update:model-value="val => savePreference('revealDottedOTP', val)" fieldName="revealDottedOTP" label="settings.forms.reveal_dotted_otp.label" help="settings.forms.reveal_dotted_otp.help" :isDisabled="!user.preferences.showOtpAsDot" :isIndented="true" />
                         <h4 class="title is-4 pt-4 has-text-grey-light">{{ $t('settings.data_input') }}</h4>
                         <h4 class="title is-4 pt-4 has-text-grey-light">{{ $t('settings.data_input') }}</h4>
                         <!-- basic qrcode -->
                         <!-- basic qrcode -->
                         <FormCheckbox v-model="user.preferences.useBasicQrcodeReader" @update:model-value="val => savePreference('useBasicQrcodeReader', val)" fieldName="useBasicQrcodeReader" label="settings.forms.use_basic_qrcode_reader.label" help="settings.forms.use_basic_qrcode_reader.help" />
                         <FormCheckbox v-model="user.preferences.useBasicQrcodeReader" @update:model-value="val => savePreference('useBasicQrcodeReader', val)" fieldName="useBasicQrcodeReader" label="settings.forms.use_basic_qrcode_reader.label" help="settings.forms.use_basic_qrcode_reader.help" />

+ 12 - 1
resources/js/views/twofaccounts/Accounts.vue

@@ -34,6 +34,7 @@
     const isDragging = ref(false)
     const isDragging = ref(false)
     const isRenewingOTPs = ref(false)
     const isRenewingOTPs = ref(false)
     const renewedPeriod = ref(null)
     const renewedPeriod = ref(null)
+    const revealPassword = ref(null)
 
 
     const otpDisplay = ref(null)
     const otpDisplay = ref(null)
     const otpDisplayProps = ref({
     const otpDisplayProps = ref({
@@ -398,7 +399,7 @@
                                             <FontAwesomeIcon :icon="['fas', 'circle-notch']" spin />
                                             <FontAwesomeIcon :icon="['fas', 'circle-notch']" spin />
                                         </span>
                                         </span>
                                         <span v-else class="always-on-otp is-clickable has-nowrap has-text-grey is-size-5 ml-4" @click="copyToClipboard(account.otp.password)" @keyup.enter="copyToClipboard(account.otp.password)" :title="$t('commons.copy_to_clipboard')">
                                         <span v-else class="always-on-otp is-clickable has-nowrap has-text-grey is-size-5 ml-4" @click="copyToClipboard(account.otp.password)" @keyup.enter="copyToClipboard(account.otp.password)" :title="$t('commons.copy_to_clipboard')">
-                                            {{ useDisplayablePassword(account.otp.password) }}
+                                            {{ useDisplayablePassword(account.otp.password, user.preferences.showOtpAsDot && user.preferences.revealDottedOTP && revealPassword == account.id) }}
                                         </span>
                                         </span>
                                         <Dots
                                         <Dots
                                             v-if="account.otp_type.includes('totp')"
                                             v-if="account.otp_type.includes('totp')"
@@ -416,6 +417,16 @@
                                     </span>
                                     </span>
                                 </div>
                                 </div>
                             </transition>
                             </transition>
+                            <transition name="popLater" v-if="user.preferences.showOtpAsDot && user.preferences.revealDottedOTP">
+                                <div v-show="user.preferences.getOtpOnRequest == false && !bus.inManagementMode" class="has-text-right">
+                                    <button v-if="revealPassword == account.id" class="pr-0 button is-ghost has-text-grey-dark" @click.stop="revealPassword = null">
+                                        <font-awesome-icon :icon="['fas', 'eye']" />
+                                    </button>
+                                    <button v-else class="pr-0 button is-ghost has-text-grey-dark" @click.stop="revealPassword = account.id">
+                                        <font-awesome-icon :icon="['fas', 'eye-slash']" />
+                                    </button>
+                                </div>
+                            </transition>
                             <transition name="fadeInOut">
                             <transition name="fadeInOut">
                                 <div class="tfa-cell tfa-edit has-text-grey" v-if="bus.inManagementMode">
                                 <div class="tfa-cell tfa-edit has-text-grey" v-if="bus.inManagementMode">
                                     <UseColorMode v-slot="{ mode }">
                                     <UseColorMode v-slot="{ mode }">

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

@@ -58,6 +58,10 @@ return [
             'label' => 'Show generated <abbr title="One-Time Password">OTP</abbr> as dot',
             'label' => 'Show generated <abbr title="One-Time Password">OTP</abbr> as dot',
             'help' => 'Replace generated password caracters with *** to ensure confidentiality. Do not affect the copy/paste feature'
             'help' => 'Replace generated password caracters with *** to ensure confidentiality. Do not affect the copy/paste feature'
         ],
         ],
+        'reveal_dotted_otp' => [
+            'label' => 'Reveal obscured <abbr title="One-Time Password">OTP</abbr>',
+            'help' => 'Let the ability to temporarily reveal Dot-Obscured passwords'
+        ],
         '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' => 'Clicking a generated password to copy it automatically hide it from the screen'

+ 1 - 0
resources/lang/en/twofaccounts.php

@@ -31,6 +31,7 @@ return [
     'accounts_deleted' => 'Account(s) successfully deleted',
     'accounts_deleted' => 'Account(s) successfully deleted',
     'accounts_moved' => 'Account(s) successfully moved',
     'accounts_moved' => 'Account(s) successfully moved',
     'export_selected_to_json' => 'Download a json export of selected accounts',
     'export_selected_to_json' => 'Download a json export of selected accounts',
+    'reveal' => 'reveal',
     'forms' => [
     'forms' => [
         'service' => [
         'service' => [
             'placeholder' => 'Google, Twitter, Apple',
             'placeholder' => 'Google, Twitter, Apple',