web/satellite: password strength implemented on register page (#3669)
This commit is contained in:
parent
2a13a7764f
commit
d9a23b8727
@ -17,6 +17,8 @@
|
||||
:placeholder="placeholder"
|
||||
:type="type"
|
||||
:style="style.inputStyle"
|
||||
@focus="showPasswordStrength"
|
||||
@blur="hidePasswordStrength"
|
||||
/>
|
||||
<!--2 conditions of eye image (crossed or not) -->
|
||||
<PasswordHiddenIcon
|
||||
@ -86,6 +88,14 @@ export default class HeaderlessInput extends Vue {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public showPasswordStrength(): void {
|
||||
this.$emit('showPasswordStrength');
|
||||
}
|
||||
|
||||
public hidePasswordStrength(): void {
|
||||
this.$emit('hidePasswordStrength');
|
||||
}
|
||||
|
||||
// triggers on input
|
||||
public onInput({ target }): void {
|
||||
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 RegistrationSuccessPopup from '@/components/common/RegistrationSuccessPopup.vue';
|
||||
import VInfo from '@/components/common/VInfo.vue';
|
||||
|
||||
import AuthIcon from '@/../static/images/AuthImage.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 { LocalData } from '@/utils/localData';
|
||||
import { validateEmail, validatePassword } from '@/utils/validation';
|
||||
import PasswordStrength from '@/views/register/passwordStrength/PasswordStrength.vue';
|
||||
|
||||
@Component({
|
||||
components: {
|
||||
HeaderlessInput,
|
||||
RegistrationSuccessPopup,
|
||||
VInfo,
|
||||
AuthIcon,
|
||||
LogoIcon,
|
||||
InfoIcon,
|
||||
PasswordStrength,
|
||||
},
|
||||
})
|
||||
export default class RegisterArea extends Vue {
|
||||
@ -55,6 +55,8 @@ export default class RegisterArea extends Vue {
|
||||
|
||||
private readonly auth: AuthHttpApi = new AuthHttpApi();
|
||||
|
||||
public isPasswordStrengthShown: boolean = false;
|
||||
|
||||
async mounted(): Promise<void> {
|
||||
if (this.$route.query.token) {
|
||||
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> {
|
||||
if (this.isLoading) {
|
||||
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%"
|
||||
height="46px"
|
||||
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 class="register-input">
|
||||
<HeaderlessInput
|
||||
@ -71,11 +72,6 @@
|
||||
height="46px"
|
||||
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 class="register-area__submit-container">
|
||||
<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