Przeglądaj źródła

Add rules and live validation to Password Field component

Bubka 2 lat temu
rodzic
commit
acdaa73e62

+ 48 - 19
resources/js/components/FormPasswordField.vue

@@ -1,25 +1,30 @@
 <template>
     <div class="field" :class="{ 'pt-3' : hasOffset }">
-        <label :for="this.inputId(inputType,fieldName)" class="label" v-html="label"></label>
-        <div class="field has-addons">
-            <div class="control">
-                <input :disabled="isDisabled" :id="this.inputId(inputType,fieldName)" :type="inputType" class="input" v-model="form[fieldName]" :placeholder="placeholder" v-bind="$attrs" v-on:change="$emit('field-changed', form[fieldName])"/>
-            </div>
-            <div class="control">
-                <a v-if="!showPassword" class="button is-dark field-lock" @click="showPassword = true" :title="$t('auth.forms.reveal_password')">
-                    <span class="icon">
-                        <font-awesome-icon :icon="['fas', 'eye-slash']" />
-                    </span>
-                </a>
-                <a v-else class="button is-dark field-lock" @click="showPassword = false" :title="$t('auth.forms.hide_password')">
-                    <span class="icon">
-                        <font-awesome-icon :icon="['fas', 'eye']" />
-                    </span>
-                </a>
-            </div>
+        <label :for="this.inputId('password',fieldName)" class="label" v-html="label"></label>
+        <div class="control has-icons-right">
+            <input :disabled="isDisabled" :id="this.inputId('password',fieldName)" :type="currentType" class="input" v-model="form[fieldName]" :placeholder="placeholder" v-bind="$attrs" v-on:change="$emit('field-changed', form[fieldName])"/>
+            <span v-if="currentType == 'password'" class="icon is-small is-right is-clickable" @click="currentType = 'text'" :title="$t('auth.forms.reveal_password')">
+                <font-awesome-icon :icon="['fas', 'eye-slash']" />
+            </span>
+            <span v-else class="icon is-small is-right is-clickable" @click="currentType = 'password'" :title="$t('auth.forms.hide_password')">
+                <font-awesome-icon :icon="['fas', 'eye']" />
+            </span>
         </div>
         <field-error :form="form" :field="fieldName" />
         <p class="help" v-html="help" v-if="help"></p>
+        <div v-if="showRules" class="columns is-mobile is-size-7 mt-0">
+            <div class="column is-one-third">
+                <span class="has-text-weight-semibold">{{ $t("auth.forms.mandatory_rules") }}</span><br />
+                <span class="is-underscored" :class="{'has-background-success-dark is-dot' : IsLongEnough}"></span>{{ $t('auth.forms.is_long_enough') }}<br/>
+            </div>
+            <div class="column">
+                <span class="has-text-weight-semibold">{{ $t("auth.forms.optional_rules_you_should_follow") }}</span><br />
+                <span class="is-underscored" :class="{'has-background-success-dark is-dot' : hasLowerCase}"></span>{{ $t('auth.forms.has_lower_case') }}<br/>
+                <span class="is-underscored" :class="{'has-background-success-dark is-dot' : hasUpperCase}"></span>{{ $t('auth.forms.has_upper_case') }}<br/>
+                <span class="is-underscored" :class="{'has-background-success-dark is-dot' : hasSpecialChar}"></span>{{ $t('auth.forms.has_special_char') }}<br/>
+                <span class="is-underscored" :class="{'has-background-success-dark is-dot' : hasNumber}"></span>{{ $t('auth.forms.has_number') }}
+            </div>
+        </div>
     </div> 
 </template>
 
@@ -30,9 +35,28 @@
         
         data() {
             return {
+                currentType: this.inputType
             }
         },
 
+        computed: {
+            hasLowerCase() {
+                return /[a-z]/.test(this.form[this.fieldName])
+            },
+            hasUpperCase() {
+                return /[A-Z]/.test(this.form[this.fieldName])
+            },
+            hasNumber() {
+                return /[0-9]/.test(this.form[this.fieldName])
+            },
+            hasSpecialChar() {
+                return /[^A-Za-z0-9]/.test(this.form[this.fieldName])
+            },
+            IsLongEnough() {
+                return this.form[this.fieldName].length >= 8
+            },
+        },
+
         props: {
             label: {
                 type: String,
@@ -47,7 +71,7 @@
 
             inputType: {
                 type: String,
-                default: 'text'
+                default: 'password'
             },
 
             form: {
@@ -73,7 +97,12 @@
             isDisabled: {
                 type: Boolean,
                 default: false
-            }
+            },
+
+            showRules: {
+                type: Boolean,
+                default: false
+            },
         },
     }
 </script>

+ 1 - 1
resources/js/views/auth/Login.vue

@@ -19,7 +19,7 @@
             <div v-if="isTesting" class="notification is-warning has-text-centered is-radiusless" v-html="$t('auth.forms.welcome_to_testing_app_use_those_credentials')" />
             <form id="frmLegacyLogin" @submit.prevent="handleSubmit" @keydown="form.onKeydown($event)">
                 <form-field :form="form" fieldName="email" inputType="email" :label="$t('auth.forms.email')" autofocus />
-                <form-field :form="form" fieldName="password" inputType="password" :label="$t('auth.forms.password')" />
+                <form-password-field :form="form" fieldName="password" :label="$t('auth.forms.password')" />
                 <form-buttons :isBusy="form.isBusy" :caption="$t('auth.sign_in')" :submitId="'btnSignIn'"/>
             </form>
             <div class="nav-links">

+ 3 - 2
resources/js/views/auth/Register.vue

@@ -25,8 +25,8 @@
             <form @submit.prevent="handleRegisterSubmit" @keydown="registerForm.onKeydown($event)">
                 <form-field :form="registerForm" fieldName="name" inputType="text" :label="$t('auth.forms.name')" autofocus />
                 <form-field :form="registerForm" fieldName="email" inputType="email" :label="$t('auth.forms.email')" />
-                <form-field :form="registerForm" fieldName="password" inputType="password" :label="$t('auth.forms.password')" />
-                <form-field :form="registerForm" fieldName="password_confirmation" inputType="password" :label="$t('auth.forms.confirm_password')" />
+                <form-password-field :form="registerForm" fieldName="password" :showRules="true" :label="$t('auth.forms.password')" />
+                <!-- <form-field :form="registerForm" fieldName="password_confirmation" inputType="password" :label="$t('auth.forms.confirm_password')" /> -->
                 <form-buttons :isBusy="registerForm.isBusy" :isDisabled="registerForm.isDisabled" :caption="$t('auth.register')" :submitId="'btnRegister'" />
             </form>
             <div class="nav-links">
@@ -66,6 +66,7 @@
              */
             async handleRegisterSubmit(e) {
                 e.preventDefault()
+                this.registerForm.password_confirmation = this.registerForm.password
 
                 this.registerForm.post('/user', {returnError: true})
                 .then(response => {

+ 1 - 1
resources/js/views/settings/Account.vue

@@ -16,7 +16,7 @@
                 <form @submit.prevent="submitPassword" @keydown="formPassword.onKeydown($event)">
                     <h4 class="title is-4 pt-6 has-text-grey-light">{{ $t('settings.change_password') }}</h4>
                     <fieldset :disabled="isRemoteUser">
-                        <form-field :form="formPassword" fieldName="password" inputType="password" :label="$t('auth.forms.new_password')" />
+                        <form-password-field :form="formPassword" fieldName="password" :showRules="true" :label="$t('auth.forms.new_password')" />
                         <form-field :form="formPassword" fieldName="password_confirmation" inputType="password" :label="$t('auth.forms.confirm_new_password')" />
                         <form-field :form="formPassword" fieldName="currentPassword" inputType="password" :label="$t('auth.forms.current_password.label')" :help="$t('auth.forms.current_password.help')" />
                         <form-buttons :isBusy="formPassword.isBusy" :caption="$t('auth.forms.change_password')" />

+ 8 - 1
resources/lang/en/auth.php

@@ -102,13 +102,20 @@ return [
         'profile_saved' => 'Profile successfully updated!',
         'welcome_to_demo_app_use_those_credentials' => 'Welcome to the 2FAuth demo.<br><br>You can connect using the email address <strong>demo@2fauth.app</strong> and the password <strong>demo</strong>',
         'welcome_to_testing_app_use_those_credentials' => 'Welcome to the 2FAuth testing instance.<br><br>Use email address <strong>testing@2fauth.app</strong> and password <strong>password</strong>',
-        'register_punchline' => 'Welcome to 2FAuth.<br/>You need an account to go further. Fill this form to register yourself, and please, choose a strong password, 2FA data are sensitives.',
+        'register_punchline' => 'Welcome to <b>2FAuth</b>.<br/>You need an account to go further, please register yourself.',
         'reset_punchline' => '2FAuth will send you a password reset link to this address. Click the link in the received email to set a new password.',
         'name_this_device' => 'Name this device',
         'delete_account' => 'Delete account',
         'delete_your_account' => 'Delete your account',
         'delete_your_account_and_reset_all_data' => 'This will reset 2FAuth. Your user account will be deleted as well as all 2FA data. There is no going back.',
         'user_account_successfully_deleted' => 'User account successfully deleted',
+        'has_lower_case' => 'Has lower case',
+        'has_upper_case' => 'Has upper case',
+        'has_special_char' => 'Has special char',
+        'has_number' => 'Has number',
+        'is_long_enough' => '8 characters min.',
+        'mandatory_rules' => 'Mandatory rules',
+        'optional_rules_you_should_follow' => 'Optional rules (highly recommanded)',
     ],
 
 ];

+ 13 - 0
resources/sass/app.scss

@@ -386,6 +386,19 @@ figure.no-icon {
   color: hsl(0, 0%, 48%);
 }
 
+.is-underscored {
+  border-bottom: 1px solid hsl(0, 0%, 29%);
+  height: 0.6rem;
+  width: 0.6rem;
+  display: inline-block;
+  margin-right: 5px;
+}
+
+  .is-underscored.is-dot {
+    border: none;
+    border-radius: 50%;
+  }
+
 .is-toggle.buttons,
 .is-toggle.buttons a.button {
   margin-bottom: 0 !important;