Skip to content

Commit

Permalink
web/satellite/v2: added missing password helper on sign up
Browse files Browse the repository at this point in the history
Issue:
#6632

Change-Id: I8741f04f520fee99801f812324fa8d371b97a4dd
  • Loading branch information
VitaliiShpital committed Jan 9, 2024
1 parent 7852a6b commit 49b83ff
Show file tree
Hide file tree
Showing 3 changed files with 247 additions and 24 deletions.
206 changes: 206 additions & 0 deletions web/satellite/vuetify-poc/src/components/PasswordStrength.vue
@@ -0,0 +1,206 @@
// Copyright (C) 2024 Storj Labs, Inc.
// See LICENSE for copying information.

<template>
<v-card
class="positioning"
width="100%"
position="absolute"
elevation="12"
variant="elevated"
>
<v-card-title class="pb-1">Password strength</v-card-title>
<v-card-subtitle>
<template #default>
<p :style="strengthLabelColor">{{ passwordStrength }}</p>
</template>
</v-card-subtitle>
<v-card-item>
<v-progress-linear :model-value="barWidth" :color="passwordStrengthColor" />
</v-card-item>
<v-card-subtitle>Your password should contain:</v-card-subtitle>
<v-card-item class="py-0">
<v-radio
tabindex="-1"
class="no-pointer-events"
:model-value="isPasswordLengthAcceptable"
color="success"
:label="`Between ${passMinLength} and ${passMaxLength} Latin characters`"
/>
</v-card-item>
<v-card-subtitle>Its nice to have:</v-card-subtitle>
<v-card-item class="py-0">
<v-radio
tabindex="-1"
class="no-pointer-events"
:model-value="hasLowerAndUpperCaseLetters"
color="success"
label="Upper & lowercase letters"
/>
</v-card-item>
<v-card-item class="py-0">
<v-radio
tabindex="-1"
class="no-pointer-events"
:model-value="hasSpecialCharacter"
color="success"
label="At least one special character"
/>
</v-card-item>
<v-card-text class="pb-2">
Avoid using a password that you use on other websites or that might be easily guessed by someone else.
</v-card-text>
</v-card>
</template>

<script setup lang="ts">
import { computed } from 'vue';
import {
VCard,
VCardTitle,
VCardSubtitle,
VCardItem,
VCardText,
VProgressLinear,
VRadio,
} from 'vuetify/components';
import { useConfigStore } from '@/store/modules/configStore';
const configStore = useConfigStore();
const PASSWORD_STRENGTH = {
veryStrong: 'Very Strong',
strong: 'Strong',
good: 'Good',
weak: 'Weak',
};
const PASSWORD_STRENGTH_COLORS = {
[PASSWORD_STRENGTH.good]: '#ffa500',
[PASSWORD_STRENGTH.strong]: '#aaff00',
[PASSWORD_STRENGTH.veryStrong]: '#008000',
default: '#ff0000',
};
const BAR_WIDTH = {
[PASSWORD_STRENGTH.weak]: '25',
[PASSWORD_STRENGTH.good]: '50',
[PASSWORD_STRENGTH.strong]: '75',
[PASSWORD_STRENGTH.veryStrong]: '100',
default: '0',
};
const props = defineProps<{
password: string;
}>();
/**
* Returns the maximum password length from the store.
*/
const passMaxLength = computed((): number => {
return configStore.state.config.passwordMaximumLength;
});
/**
* Returns the minimum password length from the store.
*/
const passMinLength = computed((): number => {
return configStore.state.config.passwordMinimumLength;
});
const isPasswordLengthAcceptable = computed((): boolean => {
return props.password.length <= passMaxLength.value
&& props.password.length >= passMinLength.value;
});
/**
* Returns password strength label depends on score.
*/
const passwordStrength = computed((): string => {
if (props.password.length < passMinLength.value) {
return `Use ${passMinLength.value} or more characters`;
}
if (props.password.length > passMaxLength.value) {
return `Use ${passMaxLength.value} or fewer characters`;
}
const score = scorePassword();
if (score > 90) {
return PASSWORD_STRENGTH.veryStrong;
}
if (score > 70) {
return PASSWORD_STRENGTH.strong;
}
if (score > 45) {
return PASSWORD_STRENGTH.good;
}
return PASSWORD_STRENGTH.weak;
});
/**
* Color for indicator between red as weak and green as strong password.
*/
const passwordStrengthColor = computed((): string => {
return PASSWORD_STRENGTH_COLORS[passwordStrength.value] || PASSWORD_STRENGTH_COLORS.default;
});
/**
* Fills password strength indicator bar.
*/
const barWidth = computed((): string => {
return BAR_WIDTH[passwordStrength.value] || BAR_WIDTH.default;
});
const strengthLabelColor = computed((): { color: string } => {
return { color: passwordStrengthColor.value };
});
const hasLowerAndUpperCaseLetters = computed((): boolean => {
return /[a-z]/.test(props.password) && /[A-Z]/.test(props.password);
});
const hasSpecialCharacter = computed((): boolean => {
return /\W/.test(props.password);
});
/**
* Returns password strength score depends on length, case variations and special characters.
*/
function scorePassword(): number {
const password = props.password;
let score = 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 = 0;
variations.forEach((check) => {
variationCount += check ? 1 : 0;
});
score += variationCount * 10;
return score;
}
</script>

<style scoped lang="scss">
.positioning {
top: calc(100% - 20px);
left: 0;
z-index: 1;
}
</style>
16 changes: 12 additions & 4 deletions web/satellite/vuetify-poc/src/styles/styles.scss
Expand Up @@ -152,7 +152,7 @@ body {
}
.v-card--variant-flat {
box-shadow: none !important;
}
}

// Cards Padding
.v-card-item {
Expand Down Expand Up @@ -427,7 +427,7 @@ table {

// Card Preview Img
.card-preview-img {
transition: transform 0.15s ease-in-out;
transition: transform 0.15s ease-in-out;
border-bottom: 1px solid rgba(var(--v-theme-on-background), 0.02);
}
.card-preview-img:hover {
Expand All @@ -443,7 +443,7 @@ table {
border-radius: 10%;
}
.card-preview-icon, .card-preview-icon img {
transition: all 0.15s ease-in-out;
transition: all 0.15s ease-in-out;
}
.card-preview-icon:hover {
background: none;
Expand All @@ -463,4 +463,12 @@ table {
// Fix label position on fields
.v-field--variant-outlined .v-field__outline .v-field__outline__start {
flex-basis: calc(var(--v-field-padding-start) - 4px) !important;
}
}

.no-pointer-events {
pointer-events: none;
}

.no-position {
position: unset !important;
}
49 changes: 29 additions & 20 deletions web/satellite/vuetify-poc/src/views/Signup.vue
Expand Up @@ -6,7 +6,7 @@
<v-container v-else class="fill-height flex-row justify-center align-center">
<v-row align="top" justify="center" class="v-col-12">
<v-col cols="12" sm="10" md="7" lg="5">
<v-card title="Create your free account" subtitle="Get 25GB storage and 25GB download per month" class="pa-2 pa-sm-7">
<v-card title="Create your free account" subtitle="Get 25GB storage and 25GB download per month" class="pa-2 pa-sm-7 overflow-visible">
<v-card-item>
<v-alert
v-if="isInvited"
Expand Down Expand Up @@ -67,24 +67,31 @@
required
/>

<v-text-field
id="Password"
v-model="password"
class="mb-2"
label="Password"
placeholder="Enter a password"
color="secondary"
:type="showPassword ? 'text' : 'password'"
:rules="passwordRules"
>
<template #append-inner>
<password-input-eye-icons
:is-visible="showPassword"
type="password"
@toggleVisibility="showPassword = !showPassword"
/>
</template>
</v-text-field>
<div class="pos-relative">
<v-text-field
id="Password"
v-model="password"
class="mb-2"
label="Password"
placeholder="Enter a password"
color="secondary"
:type="showPassword ? 'text' : 'password'"
:rules="passwordRules"
@update:focused="showPasswordStrength = !showPasswordStrength"
>
<template #append-inner>
<password-input-eye-icons
:is-visible="showPassword"
type="password"
@toggleVisibility="showPassword = !showPassword"
/>
</template>
</v-text-field>
<password-strength
v-if="showPasswordStrength"
:password="password"
/>
</div>

<v-text-field
id="Retype Password"
Expand Down Expand Up @@ -187,7 +194,7 @@
/>
</v-col>
<v-col v-if="viewConfig" cols="12" sm="10" md="7" lg="5">
<v-card variant="flat" class="pa-2 pa-sm-7 h-100">
<v-card variant="flat" class="pa-2 pa-sm-7 h-100 no-position">
<v-card-item>
<v-card-title class="text-wrap">
{{ viewConfig.title }}
Expand Down Expand Up @@ -249,6 +256,7 @@ import { useNotify } from '@/utils/hooks';

import SignupConfirmation from '@poc/views/SignupConfirmation.vue';
import PasswordInputEyeIcons from '@poc/components/PasswordInputEyeIcons.vue';
import PasswordStrength from '@poc/components/PasswordStrength.vue';

type ViewConfig = {
title: string;
Expand Down Expand Up @@ -277,6 +285,7 @@ const acceptedTerms = ref(false);
const showPassword = ref(false);
const captchaError = ref(false);
const confirmCode = ref(false);
const showPasswordStrength = ref(false);

const signupID = ref('');
const partner = ref('');
Expand Down

0 comments on commit 49b83ff

Please sign in to comment.