Skip to content

Commit

Permalink
password strength indicator (#7)
Browse files Browse the repository at this point in the history
  • Loading branch information
thecarlo authored Apr 29, 2024
1 parent fa8560a commit e9af665
Show file tree
Hide file tree
Showing 7 changed files with 186 additions and 3 deletions.
2 changes: 1 addition & 1 deletion src/components/GeneratePassword/index.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ afterEach(cleanup);
describe('GeneratePassword', () => {
const passphraseDefaultLength = 3;

const passwordDefaultLength = 15;
const passwordDefaultLength = 12;

it('should render the Generate Button', () => {
render(
Expand Down
79 changes: 77 additions & 2 deletions src/components/GeneratePassword/index.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
/* eslint-disable @typescript-eslint/indent */
import React, { useEffect, useRef, useState } from 'react';
import { CssClassType } from '@enums/cssClassType';
import { checkPasswordStrength } from '@functions/checkPasswordStrength';
import { getClassByStrength } from '@functions/checkPasswordStrength/getClassByStrength';
import { getClassNameByStrength } from '@functions/checkPasswordStrength/getClassnameByStrength';
import { estimateCrackTime } from '@functions/estimateCrackTime';

import { PasswordMode } from '../../enums/passwordMode';
import { randomPassword } from '../../functions/randomPassword';
Expand All @@ -15,7 +18,7 @@ export const GeneratePassword = (props: GeneratePasswordProps) => {

const passphraseDefaultLength = 3;

const passwordDefaultLength = 15;
const passwordDefaultLength = 12;

const defaultLength =
mode === PasswordMode.Password
Expand Down Expand Up @@ -265,7 +268,7 @@ export const GeneratePassword = (props: GeneratePasswordProps) => {
<input
type="range"
className="w-full"
min={passwordMode === PasswordMode.Password ? 12 : 3}
min={passwordMode === PasswordMode.Password ? 11 : 3}
max={passwordMode === PasswordMode.Password ? 100 : 10}
value={length}
onChange={(ev: React.ChangeEvent<HTMLInputElement>) => {
Expand Down Expand Up @@ -312,6 +315,78 @@ export const GeneratePassword = (props: GeneratePasswordProps) => {
</div>
</button>
</div>

<div
id="indicator"
className="relative w-full p-6 overflow-hidden shadow-lg rounded-xl md:w-80 dark:bg-gray-800 mx-auto mt-16 mb-12"
>
{/* <p className="text-xl text-gray-400">Password Strength</p> */}
<div className="flex items-center justify-between my-0 text-white rounded">
<span
title="Password Strength"
className={`p-2 ${getClassByStrength(
passwordStrength,
CssClassType.Background
)} rounded-lg`}
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="25"
height="25"
fill="currentColor"
viewBox="0 0 16 16"
>
<path d="M5.338 1.59a61 61 0 0 0-2.837.856.48.48 0 0 0-.328.39c-.554 4.157.726 7.19 2.253 9.188a10.7 10.7 0 0 0 2.287 2.233c.346.244.652.42.893.533q.18.085.293.118a1 1 0 0 0 .101.025 1 1 0 0 0 .1-.025q.114-.034.294-.118c.24-.113.547-.29.893-.533a10.7 10.7 0 0 0 2.287-2.233c1.527-1.997 2.807-5.031 2.253-9.188a.48.48 0 0 0-.328-.39c-.651-.213-1.75-.56-2.837-.855C9.552 1.29 8.531 1.067 8 1.067c-.53 0-1.552.223-2.662.524zM5.072.56C6.157.265 7.31 0 8 0s1.843.265 2.928.56c1.11.3 2.229.655 2.887.87a1.54 1.54 0 0 1 1.044 1.262c.596 4.477-.787 7.795-2.465 9.99a11.8 11.8 0 0 1-2.517 2.453 7 7 0 0 1-1.048.625c-.28.132-.581.24-.829.24s-.548-.108-.829-.24a7 7 0 0 1-1.048-.625 11.8 11.8 0 0 1-2.517-2.453C1.928 10.487.545 7.169 1.141 2.692A1.54 1.54 0 0 1 2.185 1.43 63 63 0 0 1 5.072.56" />
<path d="M9.5 6.5a1.5 1.5 0 0 1-1 1.415l.385 1.99a.5.5 0 0 1-.491.595h-.788a.5.5 0 0 1-.49-.595l.384-1.99a1.5 1.5 0 1 1 2-1.415" />
</svg>
</span>

<div className="flex flex-col items-start w-full ml-2 justify-evenly">
<p
className={`text-lg ${getClassByStrength(
passwordStrength,
CssClassType.Text
)}`}
>
{passwordStrength}
</p>

<p className="text-base text-gray-400">Strength</p>
</div>
</div>

<div className="flex items-center justify-between text-white rounded mt-4">
<span
className={`p-2 ${getClassByStrength(
passwordStrength,
CssClassType.Background
)} rounded-lg`}
title="Time to crack password"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="25"
height="25"
fill="currentColor"
viewBox="0 0 16 16"
>
<path d="M8 3.5a.5.5 0 0 0-1 0V9a.5.5 0 0 0 .252.434l3.5 2a.5.5 0 0 0 .496-.868L8 8.71z" />
<path d="M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16m7-8A7 7 0 1 1 1 8a7 7 0 0 1 14 0" />
</svg>
</span>
<div className="flex flex-col items-start w-full ml-2 justify-evenly">
<p
className={`text-lg ${getClassByStrength(
passwordStrength,
CssClassType.Text
)}`}
>
{estimateCrackTime(password)}
</p>
<p className="text-base text-gray-400">Time to crack password</p>
</div>
</div>
</div>
</div>
);
};
4 changes: 4 additions & 0 deletions src/enums/cssClassType.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export const enum CssClassType {
Text = 'Text',
Background = 'Background',
}
25 changes: 25 additions & 0 deletions src/functions/checkPasswordStrength/getClassByStrength.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { CssClassType } from '@enums/cssClassType';

export const getClassByStrength = (
strength: string,
classType: CssClassType
): string => {
const cssPrefix = classType === CssClassType.Text ? 'text' : 'bg';

switch (strength) {
case 'Weak':
return `${cssPrefix}-red-500`;

case 'Moderate':
return `${cssPrefix}-yellow-500`;

case 'Strong':
return `${cssPrefix}-green-500`;

case 'Very Strong':
return `${cssPrefix}-green-500`;

default:
return `${cssPrefix}-red-500`;
}
};
43 changes: 43 additions & 0 deletions src/functions/crackTimes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { CrackTime } from '@interfaces/crackTime';

export const crackTimes: Record<number, CrackTime> = {
8: {
allChars: '28 seconds',
onlyAlphaNum: '2 minutes',
onlyAlpha: '5 minutes',
},
9: { allChars: '6 hours', onlyAlphaNum: '2 hours', onlyAlpha: '24 minutes' },
10: { allChars: '2 weeks', onlyAlphaNum: '5 days', onlyAlpha: '21 hours' },
11: { allChars: '3 years', onlyAlphaNum: '10 months', onlyAlpha: '1 month' },
12: { allChars: '226 years', onlyAlphaNum: '53 years', onlyAlpha: '6 years' },
13: {
allChars: '15 thousand years',
onlyAlphaNum: '3 thousand years',
onlyAlpha: '332 years',
},
14: {
allChars: '1 million years',
onlyAlphaNum: '200 thousand years',
onlyAlpha: '17 thousand years',
},
15: {
allChars: '77 million years',
onlyAlphaNum: '12 million years',
onlyAlpha: '900 thousand years',
},
16: {
allChars: '5 billion years',
onlyAlphaNum: '779 million years',
onlyAlpha: '46 million years',
},
17: {
allChars: '380 billion years',
onlyAlphaNum: '48 billion years',
onlyAlpha: '2 billion years',
},
18: {
allChars: '26 trillion years',
onlyAlphaNum: '2 trillion years',
onlyAlpha: '126 billion years',
},
};
31 changes: 31 additions & 0 deletions src/functions/estimateCrackTime.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { CrackTime } from '@interfaces/crackTime';

import { crackTimes } from './crackTimes';

export const estimateCrackTime = (password: string): string => {
const hasUpper = /[A-Z]/.test(password);

const hasLower = /[a-z]/.test(password);

const hasNumbers = /[0-9]/.test(password);

const hasSpecial = /[!@#$%^&*()_+\-=[\]{}|;:'",.<>?]/.test(password);

let key: keyof CrackTime = 'onlyAlpha';

if (hasUpper && hasLower && hasNumbers && hasSpecial) {
key = 'allChars';
} else if (hasUpper && hasLower && hasNumbers) {
key = 'onlyAlphaNum';
}

const length = password.length;

if (length > 18) {
return 'Trillions of years';
} else if (length >= 8 && crackTimes[length]) {
return crackTimes[length][key];
}

return 'Not specified';
};
5 changes: 5 additions & 0 deletions src/interfaces/crackTime.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export interface CrackTime {
allChars: string;
onlyAlphaNum: string;
onlyAlpha: string;
}

0 comments on commit e9af665

Please # to comment.