Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
180 changes: 109 additions & 71 deletions components/PasswordStrengthChecker.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,15 @@

import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { View, TextInput, StyleSheet, Dimensions, Text, Animated } from 'react-native';
import {
View,
TextInput,
StyleSheet,
Dimensions,
Text,
Animated,
ViewPropTypes,
} from 'react-native';
import zxcvbn from 'zxcvbn';
import _ from 'lodash';

Expand All @@ -21,7 +29,7 @@ const regex = {
lowerCasePattern: /[a-z]/,
upperCasePattern: /[A-Z]/,
wordsPattern: /\w/,
symbolsPattern: /\W/
symbolsPattern: /\W/,
};

export default class PasswordStrengthChecker extends Component {
Expand All @@ -34,117 +42,118 @@ export default class PasswordStrengthChecker extends Component {
label: 'Weak',
labelColor: '#fff',
widthPercent: 33,
innerBarColor: '#fe6c6c'
innerBarColor: '#fe6c6c',
},
{
label: 'Weak',
labelColor: '#fff',
widthPercent: 33,
innerBarColor: '#fe6c6c'
innerBarColor: '#fe6c6c',
},
{
label: 'Fair',
labelColor: '#fff',
widthPercent: 67,
innerBarColor: '#feb466'
innerBarColor: '#feb466',
},
{
label: 'Fair',
labelColor: '#fff',
widthPercent: 67,
innerBarColor: '#feb466'
innerBarColor: '#feb466',
},
{
label: 'Strong',
labelColor: '#fff',
widthPercent: 100,
innerBarColor: '#6cfeb5'
}
innerBarColor: '#6cfeb5',
},
],
tooShort: {
enabled: false,
labelColor: '#fff',
label: 'Too short',
widthPercent: 33,
innerBarColor: '#fe6c6c'
innerBarColor: '#fe6c6c',
},
barColor: '#ffffff',
barWidthPercent: 70,
showBarOnEmpty: true
showBarOnEmpty: true,
};

static propTypes = {
onChangeText: PropTypes.func.isRequired,
minLength: PropTypes.number,
ruleNames: PropTypes.string,
strengthLevels: PropTypes.array,
tooShort: PropTypes.object,
minLevel: PropTypes.number,
inputWrapperStyle: View.propTypes.style,
inputWrapperStyle: ViewPropTypes.style,
inputStyle: TextInput.propTypes.style,
strengthWrapperStyle: View.propTypes.style,
strengthBarStyle: View.propTypes.style,
innerStrengthBarStyle: View.propTypes.style,
strengthWrapperStyle: ViewPropTypes.style,
strengthBarStyle: ViewPropTypes.style,
innerStrengthBarStyle: ViewPropTypes.style,
strengthDescriptionStyle: Text.propTypes.style,
barColor: PropTypes.string,
barWidthPercent: PropTypes.number,
showBarOnEmpty: PropTypes.bool
showBarOnEmpty: PropTypes.bool,
};

constructor(props) {
super(props);
this.animatedInnerBarWidth = new Animated.Value(0);
this.animatedBarWidth = new Animated.Value(0);
this.state = {
level: -1,
isTooShort: false
}
isTooShort: false,
};
}

componentDidMount() {
const { showBarOnEmpty } = this.props;
if (showBarOnEmpty) {
this.showFullBar();
}
}

showFullBar(isShow = true) {
const { barWidthPercent } = this.props;
const barWidth = isShow ? widthByPercent(barWidthPercent) : 0;
Animated.timing(this.animatedBarWidth, {
toValue: barWidth,
duration: 20
duration: 20,
}).start();
}

isTooShort(password) {
const { minLength } = this.props;
if (!minLength) {
return true;
}
return password.length < minLength;
}

isMatchingRules(password) {
const { ruleNames } = this.props;
if (!ruleNames) {
return true;
}

const rules = _.chain(ruleNames)

const rules = _
.chain(ruleNames)
.split('|')
.filter(rule => !!rule)
.map(rule => rule.trim())
.value();

for (const rule of rules) {
if (!this.isMatchingRule(password, rule)) {
return false;
}
}
return true;
}

isMatchingRule(password, rule) {
switch (rule) {
case 'symbols':
Expand All @@ -163,46 +172,47 @@ export default class PasswordStrengthChecker extends Component {
return true;
}
}

calculateScore(text) {
if (!text) {
this.setState({
isTooShort: false
isTooShort: false,
});
return -1;
}

if (this.isTooShort(text)) {
this.setState({
isTooShort: true
isTooShort: true,
});
return 0;
}

this.setState({
isTooShort: false
isTooShort: false,
});

if (!this.isMatchingRules(text)) {
return 0;
}

return zxcvbn(text).score;
}

getPasswordStrengthLevel(password) {
return this.calculateScore(password);
}

onChangeText(password) {
const level = this.getPasswordStrengthLevel(password);
this.setState({
level: level
level: level,
});
const isValid = this.isMatchingRules(password) && level >= this.props.minLevel;
const isValid =
this.isMatchingRules(password) && level >= this.props.minLevel;
this.props.onChangeText(password, isValid);
}

renderPasswordInput() {
const { inputWrapperStyle, inputStyle } = this.props;
return (
Expand All @@ -220,7 +230,7 @@ export default class PasswordStrengthChecker extends Component {
</View>
);
}

renderPasswordStrength() {
const {
barWidthPercent,
Expand All @@ -231,37 +241,45 @@ export default class PasswordStrengthChecker extends Component {
strengthBarStyle,
innerStrengthBarStyle,
strengthDescriptionStyle,
showBarOnEmpty
showBarOnEmpty,
} = this.props;

const barWidth = widthByPercent(barWidthPercent);

const { level } = this.state;

let strengthLevelBarStyle = {}, strengthLevelLabelStyle = {}, strengthLevelLabel = '', innerBarWidth = 0;

let strengthLevelBarStyle = {},
strengthLevelLabelStyle = {},
strengthLevelLabel = '',
innerBarWidth = 0;
if (level !== -1) {

if (!showBarOnEmpty) {
this.showFullBar();
}

innerBarWidth = widthByPercent(strengthLevels[level].widthPercent, barWidth);

innerBarWidth = widthByPercent(
strengthLevels[level].widthPercent,
barWidth,
);
strengthLevelBarStyle = {
backgroundColor: strengthLevels[level].innerBarColor
backgroundColor: strengthLevels[level].innerBarColor,
};

strengthLevelLabelStyle = {
color: strengthLevels[level].labelColor
color: strengthLevels[level].labelColor,
};
strengthLevelLabel = strengthLevels[level].label;

if (tooShort.enabled && this.state.isTooShort) {
innerBarWidth = widthByPercent(tooShort.widthPercent, barWidth) || widthByPercent(strengthLevels[level].widthPercent, barWidth);
innerBarWidth =
widthByPercent(tooShort.widthPercent, barWidth) ||
widthByPercent(strengthLevels[level].widthPercent, barWidth);
strengthLevelBarStyle = {
backgroundColor: tooShort.innerBarColor || strengthLevels[level].innerBarColor
backgroundColor:
tooShort.innerBarColor || strengthLevels[level].innerBarColor,
};
strengthLevelLabelStyle = {
color: tooShort.labelColor || strengthLevels[level].labelColor
color: tooShort.labelColor || strengthLevels[level].labelColor,
};
strengthLevelLabel = tooShort.label || strengthLevels[level].label;
}
Expand All @@ -270,22 +288,42 @@ export default class PasswordStrengthChecker extends Component {
this.showFullBar(false);
}
}

Animated.timing(this.animatedInnerBarWidth, {
toValue: innerBarWidth,
duration: 800
duration: 800,
}).start();

return (
<View style={[styles.passwordStrengthWrapper, strengthWrapperStyle]}>
<Animated.View style={[styles.passwordStrengthBar, strengthBarStyle, { backgroundColor: barColor, width: this.animatedBarWidth }]}>
<Animated.View style={[styles.innerPasswordStrengthBar, innerStrengthBarStyle, { ...strengthLevelBarStyle, width: this.animatedInnerBarWidth }]} />
<Animated.View
style={[
styles.passwordStrengthBar,
strengthBarStyle,
{ backgroundColor: barColor, width: this.animatedBarWidth },
]}
>
<Animated.View
style={[
styles.innerPasswordStrengthBar,
innerStrengthBarStyle,
{ ...strengthLevelBarStyle, width: this.animatedInnerBarWidth },
]}
/>
</Animated.View>
<Text style={[styles.strengthDescription, strengthDescriptionStyle, { ...strengthLevelLabelStyle }]}>{strengthLevelLabel}</Text>
<Text
style={[
styles.strengthDescription,
strengthDescriptionStyle,
{ ...strengthLevelLabelStyle },
]}
>
{strengthLevelLabel}
</Text>
</View>
);
}

render() {
return (
<View style={styles.wrapper}>
Expand All @@ -307,31 +345,31 @@ const styles = StyleSheet.create({
alignItems: 'center',
backgroundColor: 'transparent',
borderBottomWidth: 0.8,
borderColor: 'rgba(242, 242, 242, 0.5)'
borderColor: 'rgba(242, 242, 242, 0.5)',
},
input: {
flex: 1,
color: '#fff',
paddingTop: 7,
paddingBottom: 10,
fontSize: 20
fontSize: 20,
},
passwordStrengthWrapper: {
flexDirection: 'row',
flexWrap: 'wrap',
marginBottom: 10
marginBottom: 10,
},
passwordStrengthBar: {
height: 10,
position: 'relative',
top: 5,
bottom: 5,
borderRadius: 5
borderRadius: 5,
},
innerPasswordStrengthBar: {
height: 10,
borderRadius: 5,
width: 0
width: 0,
},
strengthDescription: {
color: '#fff',
Expand All @@ -340,6 +378,6 @@ const styles = StyleSheet.create({
position: 'absolute',
right: 5,
top: 1,
fontSize: 14
}
fontSize: 14,
},
});