-
Notifications
You must be signed in to change notification settings - Fork 21
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
d06bc71
commit b8ca521
Showing
4 changed files
with
115 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
58 changes: 58 additions & 0 deletions
58
packages/keycloak-user-management/src/components/Credentials/passwordStrengthMeter.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
import { Input, Progress } from 'antd'; | ||
import { InputProps } from 'antd/lib/input'; | ||
import React, { useState } from 'react'; | ||
|
||
// 00-24 poor password | ||
// 25-49 weak password | ||
// 50-74 good password | ||
// 75-100 strong password | ||
// entropy source: https://www.baeldung.com/cs/password-entropy | ||
export const calculateEntropy = (password: string) => { | ||
let charSetSize = 0; | ||
if (/[a-z]/.test(password)) charSetSize += 26; | ||
if (/[A-Z]/.test(password)) charSetSize += 26; | ||
if (/[0-9]/.test(password)) charSetSize += 10; | ||
if (/\W|_/.test(password)) charSetSize += 32; | ||
return password.length > 0 ? Math.log2(charSetSize ** password.length) : 0; | ||
}; | ||
|
||
export const PasswordStrengthMeter = (props: InputProps) => { | ||
const [strength, setStrength] = useState(0); | ||
|
||
const getStrengthLevel = (entropy: number) => { | ||
if (entropy < 25) return 'poor'; | ||
if (entropy < 50) return 'weak'; | ||
if (entropy < 75) return 'good'; | ||
return 'strong'; | ||
}; | ||
|
||
const handlePasswordChange = (e: React.ChangeEvent<HTMLInputElement>) => { | ||
const newPassword = e.target.value; | ||
setStrength(calculateEntropy(newPassword)); | ||
props.onChange?.(e); | ||
}; | ||
|
||
const strengthLevel = getStrengthLevel(strength); | ||
const strengthPercent = Math.min((strength / 100) * 100, 100); | ||
const inputStrokeColor = | ||
strengthLevel === 'poor' | ||
? '#ff4d4f' | ||
: strengthLevel === 'weak' | ||
? '#faad14' | ||
: strengthLevel === 'good' | ||
? '#52c41a' | ||
: '#1890ff'; | ||
|
||
return ( | ||
<div> | ||
<Input.Password {...props} onChange={handlePasswordChange} /> | ||
<Progress | ||
data-testid={`level-${strengthLevel}`} | ||
percent={strengthPercent} | ||
status={strengthLevel === 'poor' ? 'exception' : 'active'} | ||
strokeColor={inputStrokeColor} | ||
showInfo={false} | ||
/> | ||
</div> | ||
); | ||
}; |
44 changes: 44 additions & 0 deletions
44
.../keycloak-user-management/src/components/Credentials/tests/passwordStrengthMeter.test.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
import React from 'react'; | ||
import { render, screen, fireEvent } from '@testing-library/react'; | ||
import { PasswordStrengthMeter } from '../passwordStrengthMeter'; | ||
|
||
const props = { | ||
placeholder: 'Enter your password', | ||
}; | ||
|
||
describe('PasswordStrengthMeter', () => { | ||
test('renders input field and progress bar', () => { | ||
render(<PasswordStrengthMeter {...props} />); | ||
expect(screen.getByPlaceholderText('Enter your password')).toBeInTheDocument(); | ||
expect(screen.getByRole('progressbar')).toBeInTheDocument(); | ||
}); | ||
|
||
test('updates progress bar when password is typed', () => { | ||
render(<PasswordStrengthMeter {...props} />); | ||
const input = screen.getByPlaceholderText('Enter your password'); | ||
|
||
fireEvent.change(input, { target: { value: 'password123' } }); | ||
// confirm test id has the correct value. | ||
screen.getByTestId('level-good'); | ||
}); | ||
|
||
test('changes progress bar color based on password strength', () => { | ||
render(<PasswordStrengthMeter {...props} />); | ||
const input = screen.getByPlaceholderText('Enter your password'); | ||
|
||
fireEvent.change(input, { target: { value: 'weak' } }); | ||
screen.getByTestId('level-poor'); | ||
|
||
fireEvent.change(input, { target: { value: 'Stronger1!' } }); | ||
screen.getByTestId('level-good'); | ||
}); | ||
|
||
test('calls onChange when password is entered', () => { | ||
const handleChange = jest.fn(); | ||
render(<PasswordStrengthMeter {...props} onChange={handleChange} />); | ||
const input = screen.getByPlaceholderText('Enter your password'); | ||
|
||
fireEvent.change(input, { target: { value: 'testing123' } }); | ||
expect(handleChange).toHaveBeenCalledTimes(1); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters