web/satellite: password strength implemented on register page (#3669)
This commit is contained in:
parent
2a13a7764f
commit
d9a23b8727
@ -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) {
|
||||||
|
@ -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;
|
||||||
|
@ -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>
|
@ -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">
|
||||||
|
3
web/satellite/static/images/register/StrengthVector.svg
Normal file
3
web/satellite/static/images/register/StrengthVector.svg
Normal 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 |
Loading…
Reference in New Issue
Block a user