Skip to content

Commit

Permalink
Added Password Strength Component in a seperate file
Browse files Browse the repository at this point in the history
  • Loading branch information
Csaern committed Dec 19, 2024
1 parent 4c44920 commit 428aea4
Show file tree
Hide file tree
Showing 6 changed files with 6,138 additions and 3 deletions.
126 changes: 126 additions & 0 deletions client/src/components/Login/PasswordStength.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
<script setup lang="ts">
import { ref, watch, type PropType } from "vue";
import zxcvbn from "zxcvbn"; // Import the zxcvbn library
// Props to receive the password input
const props = defineProps({
password: {
type: String as PropType<string | null>,
required: true,
},
});
const passwordStrength = ref<string>("empty");
const strengthScore = ref<number>(0);
const showPasswordGuidelines = ref(false);
// Evaluate password strength using zxcvbn
function evaluatePasswordStrength(newPassword: string) {
if (newPassword.length === 0) {
passwordStrength.value = "empty";
strengthScore.value = 0;
return;
}
const result = zxcvbn(newPassword); // Analyze the password with zxcvbn
strengthScore.value = result.score; // zxcvbn returns a score from 0 to 4
// Map zxcvbn scores to strength labels
if (strengthScore.value === 0 || strengthScore.value === 1) {
passwordStrength.value = "weak";
} else if (strengthScore.value === 2 || strengthScore.value === 3) {
passwordStrength.value = "medium";
} else {
passwordStrength.value = "strong";
}
}
// Watch for changes in the password prop
watch(() => props.password, (newPassword) => {
if (typeof newPassword === "string") {
evaluatePasswordStrength(newPassword);
}
});
</script>

<template>
<div>
<!-- Password Guidelines Button -->
<BButton variant="info" class="mt-3" @click="showPasswordGuidelines = true">
Password Guidelines
</BButton>

<!-- Password Strength Bar -->
<div class="password-strength-bar-container mt-2">
<div
class="password-strength-bar"
:class="passwordStrength"
:style="{ width: `${(strengthScore / 4) * 100}%` }"
></div>
</div>

<!-- Password Strength Text -->
<div :class="['password-strength', passwordStrength]" class="mt-2">
<span v-if="passwordStrength === 'empty'"></span>
<span v-else-if="passwordStrength === 'weak'">Weak Password</span>
<span v-else-if="passwordStrength === 'medium'">Medium Password</span>
<span v-else>Strong Password</span>
</div>

<!-- Password Guidelines -->
<BModal v-model="showPasswordGuidelines" title="Tips for a secure Password">
<p>A good password should meet the following criteria:</p>
<ul>
<li>At least 13 characters long.</li>
<li>Use uppercase and lowercase letters.</li>
<li>At least one number and one special character.</li>
<li>Avoid common passwords like <code>123456</code> or <code>password</code>.</li>
<li>No repeated patterns like <code>aaaa</code> or <code>123123</code>.</li>
</ul>
<template v-slot:modal-footer>
<BButton variant="secondary" @click="showPasswordGuidelines = false">Close</BButton>
</template>
</BModal>
</div>
</template>

<style scoped lang="scss">
@import "theme/blue.scss";
.password-strength-bar-container {
background-color: #f0f0f0;
height: 8px;
border-radius: 4px;
overflow: hidden;
margin-top: 5px;
}
.password-strength-bar {
height: 100%;
transition: width 0.3s ease;
}
.password-strength-bar.weak {
background-color: $brand-danger;
}
.password-strength-bar.medium {
background-color: $brand-warning;
}
.password-strength-bar.strong {
background-color: $brand-success;
}
.password-strength.weak {
color: $brand-danger;
}
.password-strength.medium {
color: $brand-warning;
}
.password-strength.strong {
color: $brand-success;
}
</style>
12 changes: 10 additions & 2 deletions client/src/components/Login/RegisterForm.vue
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
BFormGroup,
BFormInput,
BFormText,
BModal,
} from "bootstrap-vue";
import { computed, type Ref, ref } from "vue";
Expand All @@ -22,6 +23,7 @@ import { withPrefix } from "@/utils/redirect";
import { errorMessageAsString } from "@/utils/simple-error";
import ExternalLogin from "@/components/User/ExternalIdentities/ExternalLogin.vue";
import PasswordStrength from "@/components/Login/PasswordStength.vue";
interface Props {
sessionCsrfToken: string;
Expand All @@ -43,7 +45,7 @@ const emit = defineEmits<{
const email = ref(null);
const confirm = ref(null);
const password = ref(null);
const password = ref<string | null>(null);
const username = ref(null);
const subscribe = ref(null);
const messageText: Ref<string | null> = ref(null);
Expand Down Expand Up @@ -145,7 +147,10 @@ async function submit() {
name="password"
type="password"
autocomplete="new-password"
required />
required
/>
<!-- Password Strength Component -->
<PasswordStrength :password="password" />
</BFormGroup>

<BFormGroup :label="labelConfirmPassword" label-for="register-form-confirm">
Expand Down Expand Up @@ -217,7 +222,10 @@ async function submit() {
</div>
</div>
</template>

<style scoped lang="scss">
@import "theme/blue.scss";
.embed-container {
position: relative;
Expand Down
18 changes: 18 additions & 0 deletions client/types/zxcvbn.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
declare module 'zxcvbn' {
interface ZxcvbnFeedback {
warning: string;
suggestions: string[];
}

interface ZxcvbnResult {
crack_times_display: any;
score: number; // Password strength score (0-4)
feedback: ZxcvbnFeedback; // Feedback about the password
guesses: number; // Estimated number of guesses to crack the password
guesses_log10: number; // Log10 of the guesses
calc_time: number; // Time taken for the calculation (in milliseconds)
sequence: any[]; // Sequence of patterns used to match the password
}

export default function zxcvbn(password: string): ZxcvbnResult;
}

Large diffs are not rendered by default.

Loading

0 comments on commit 428aea4

Please sign in to comment.