web/satellite: password strength implemented on register page (#3669)

This commit is contained in:
Vitalii Shpital 2019-11-29 14:52:21 +02:00 committed by GitHub
parent 2a13a7764f
commit d9a23b8727
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 291 additions and 12 deletions

View File

@ -17,6 +17,8 @@
:placeholder="placeholder" :placeholder="placeholder"
:type="type" :type="type"
:style="style.inputStyle" :style="style.inputStyle"
@focus="showPasswordStrength"
@blur="hidePasswordStrength"
/> />
<!--2 conditions of eye image (crossed or not) --> <!--2 conditions of eye image (crossed or not) -->
<PasswordHiddenIcon <PasswordHiddenIcon
@ -86,6 +88,14 @@ export default class HeaderlessInput extends Vue {
this.value = value; this.value = value;
} }
public showPasswordStrength(): void {
this.$emit('showPasswordStrength');
}
public hidePasswordStrength(): void {
this.$emit('hidePasswordStrength');
}
// triggers on input // triggers on input
public onInput({ target }): void { public onInput({ target }): void {
if (target.value.length > this.maxSymbols) { if (target.value.length > this.maxSymbols) {

View File

@ -8,7 +8,6 @@ import { Component, Vue } from 'vue-property-decorator';
import HeaderlessInput from '@/components/common/HeaderlessInput.vue'; import HeaderlessInput from '@/components/common/HeaderlessInput.vue';
import RegistrationSuccessPopup from '@/components/common/RegistrationSuccessPopup.vue'; import RegistrationSuccessPopup from '@/components/common/RegistrationSuccessPopup.vue';
import VInfo from '@/components/common/VInfo.vue';
import AuthIcon from '@/../static/images/AuthImage.svg'; import AuthIcon from '@/../static/images/AuthImage.svg';
import InfoIcon from '@/../static/images/info.svg'; import InfoIcon from '@/../static/images/info.svg';
@ -21,15 +20,16 @@ import { APP_STATE_ACTIONS } from '@/utils/constants/actionNames';
import { LOADING_CLASSES } from '@/utils/constants/classConstants'; import { LOADING_CLASSES } from '@/utils/constants/classConstants';
import { LocalData } from '@/utils/localData'; import { LocalData } from '@/utils/localData';
import { validateEmail, validatePassword } from '@/utils/validation'; import { validateEmail, validatePassword } from '@/utils/validation';
import PasswordStrength from '@/views/register/passwordStrength/PasswordStrength.vue';
@Component({ @Component({
components: { components: {
HeaderlessInput, HeaderlessInput,
RegistrationSuccessPopup, RegistrationSuccessPopup,
VInfo,
AuthIcon, AuthIcon,
LogoIcon, LogoIcon,
InfoIcon, InfoIcon,
PasswordStrength,
}, },
}) })
export default class RegisterArea extends Vue { export default class RegisterArea extends Vue {
@ -55,6 +55,8 @@ export default class RegisterArea extends Vue {
private readonly auth: AuthHttpApi = new AuthHttpApi(); private readonly auth: AuthHttpApi = new AuthHttpApi();
public isPasswordStrengthShown: boolean = false;
async mounted(): Promise<void> { async mounted(): Promise<void> {
if (this.$route.query.token) { if (this.$route.query.token) {
this.secret = this.$route.query.token.toString(); this.secret = this.$route.query.token.toString();
@ -77,6 +79,14 @@ export default class RegisterArea extends Vue {
} }
} }
public showPasswordStrength(): void {
this.isPasswordStrengthShown = true;
}
public hidePasswordStrength(): void {
this.isPasswordStrengthShown = false;
}
public async onCreateClick(): Promise<void> { public async onCreateClick(): Promise<void> {
if (this.isLoading) { if (this.isLoading) {
return; return;

View File

@ -0,0 +1,260 @@
// Copyright (C) 2019 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<div class="password-strength-container" v-if="isShown">
<div class="password-strength-container__header">
<p class="password-strength-container__header__title">Password strength</p>
<p class="password-strength-container__header__strength-status" :style="strengthLabelColor">{{passwordStrength}}</p>
</div>
<div class="password-strength-container__bar">
<div class="password-strength-container__bar__fill" :style="barFillStyle"></div>
</div>
<p class="password-strength-container__subtitle">Your password should contain:</p>
<div class="password-strength-container__rule-area">
<div class="password-strength-container__rule-area__checkbox" :class="{ checked: isPasswordLongEnough }">
<VectorIcon/>
</div>
<p class="password-strength-container__rule-area__rule">6 or more Latin characters</p>
</div>
<p class="password-strength-container__subtitle">Its nice to have: </p>
<div class="password-strength-container__rule-area">
<div class="password-strength-container__rule-area__checkbox" :class="{ checked: hasLowerAndUpperCaseLetters }">
<VectorIcon/>
</div>
<p class="password-strength-container__rule-area__rule">Upper & lowercase letters</p>
</div>
<div class="password-strength-container__rule-area">
<div class="password-strength-container__rule-area__checkbox" :class="{ checked: hasSpecialCharacter }">
<VectorIcon/>
</div>
<p class="password-strength-container__rule-area__rule">At least one special character</p>
</div>
<p class="password-strength-container__subtitle">Avoid using a password that you use on other websities or that might be easily guessed by someone else.</p>
</div>
</template>
<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator';
import VectorIcon from '@/../static/images/register/StrengthVector.svg';
/**
* BarFillStyle class holds info for BarFillStyle entity.
*/
class BarFillStyle {
'background-color': string;
width: string;
public constructor(backgroundColor: string, width: string) {
this['background-color'] = backgroundColor;
this.width = width;
}
}
/**
* StrengthLabelColor class holds info for StrengthLabelColor entity.
*/
class StrengthLabelColor {
color: string;
public constructor(color: string) {
this.color = color;
}
}
@Component({
components: {
VectorIcon,
},
})
export default class PasswordStrength extends Vue {
@Prop({default: ''})
private readonly passwordString: string;
@Prop({default: false})
private readonly isShown: boolean;
private MINIMAL_PASSWORD_LENGTH: number = 6;
public get isPasswordLongEnough(): boolean {
return this.passwordString.length >= this.MINIMAL_PASSWORD_LENGTH;
}
public get passwordStrength(): string {
if (this.passwordString.length < this.MINIMAL_PASSWORD_LENGTH) {
return `Use ${this.MINIMAL_PASSWORD_LENGTH} or more characters`;
}
const score = this.scorePassword();
if (score > 90) {
return 'Very Strong';
}
if (score > 70) {
return 'Strong';
}
if (score > 45) {
return 'Good';
}
return 'Weak';
}
public get barFillStyle(): BarFillStyle {
return new BarFillStyle(this.passwordStrengthColor(this.passwordStrength), this.barWidth(this.passwordStrength));
}
public get strengthLabelColor(): StrengthLabelColor {
return new StrengthLabelColor(this.passwordStrengthColor(this.passwordStrength));
}
public get hasLowerAndUpperCaseLetters(): boolean {
return /[a-z]/.test(this.passwordString) && /[A-Z]/.test(this.passwordString);
}
public get hasSpecialCharacter(): boolean {
return /\W/.test(this.passwordString);
}
private scorePassword(): number {
const password: string = this.passwordString;
let score: number = 0;
const letters: number[] = [];
for (let i = 0; i < password.length; i++) {
letters[password[i]] = (letters[password[i]] || 0) + 1;
score += 5 / letters[password[i]];
}
const variations: boolean[] = [
/\d/.test(password),
/[a-z]/.test(password),
/[A-Z]/.test(password),
/\W/.test(password),
];
let variationCount: number = 0;
variations.forEach((check) => {
variationCount += check ? 1 : 0;
});
score += variationCount * 10;
return score;
}
private passwordStrengthColor(strength: string): string {
switch (strength) {
case 'Good':
return '#ffff00';
case 'Strong':
return '#bfff00';
case 'Very Strong':
return '#00ff40';
}
return '#e16c58';
}
private barWidth(strength: string): string {
switch (strength) {
case 'Weak':
return '81px';
case 'Good':
return '162px';
case 'Strong':
return '243px';
case 'Very Strong':
return '325px';
}
return '0px';
}
}
</script>
<style scoped lang="scss">
p {
margin: 0;
}
.password-strength-container {
position: absolute;
top: 95px;
right: -3px;
padding: 25px 20px;
opacity: 0.97;
border: 1px solid rgba(193, 193, 193, 0.3);
box-shadow: 0 4px 20px rgba(204, 208, 214, 0.25);
border-radius: 6px;
background-color: #fff;
height: 220px;
width: 325px;
z-index: 100;
font-family: 'font_medium', sans-serif;
&__header {
display: flex;
align-items: center;
justify-content: space-between;
font-size: 14px;
line-height: 19px;
&__title {
color: #384b65;
}
}
&__bar {
height: 3px;
width: 100%;
border-radius: 17px;
background-color: #afb7c1;
margin: 5px 0 0 0;
position: relative;
&__fill {
height: 100%;
position: absolute;
left: 0;
top: 0;
border-radius: 17px;
}
}
&__subtitle {
font-size: 12px;
line-height: 16px;
color: #afb7c1;
margin: 10px 0 0 0;
}
&__rule-area {
display: flex;
align-items: center;
justify-content: flex-start;
margin: 10px 0 0 0;
&__checkbox {
height: 20px;
width: 20px;
border-radius: 10px;
border: 1.5px solid #737791;
display: flex;
align-items: center;
justify-content: center;
}
&__rule {
font-size: 12px;
line-height: 16px;
color: #384b65;
margin: 0 0 0 5px;
}
}
}
.checked {
background-color: #27ae60;
border-color: #27ae60;
}
</style>

View File

@ -53,12 +53,13 @@
width="100%" width="100%"
height="46px" height="46px"
is-password="true" is-password="true"
@showPasswordStrength="showPasswordStrength"
@hidePasswordStrength="hidePasswordStrength"
/>
<PasswordStrength
:password-string="password"
:is-shown="isPasswordStrengthShown"
/> />
<VInfo
class="register-input__info-button"
bold-text="Use 6 or more characters with a mix of letters, numbers & symbols" >
<InfoIcon/>
</VInfo>
</div> </div>
<div class="register-input"> <div class="register-input">
<HeaderlessInput <HeaderlessInput
@ -71,11 +72,6 @@
height="46px" height="46px"
is-password="true" is-password="true"
/> />
<VInfo
class="register-input__info-button"
bold-text="Use 6 or more characters with a mix of letters, numbers & symbols">
<InfoIcon/>
</VInfo>
</div> </div>
<div class="register-area__submit-container"> <div class="register-area__submit-container">
<div class="register-area__submit-container__terms-area"> <div class="register-area__submit-container__terms-area">

View File

@ -0,0 +1,3 @@
<svg width="12" height="10" viewBox="0 0 12 10" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M11.0004 1.4043L4.00039 8.41129L1.90039 6.31129" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 246 B