UNPKG

react-native-password-strength-checker

Version:

A react-native password input with strength checker for both IOS and Android

346 lines (311 loc) 8.68 kB
/** * Created by dungtran on 8/20/17. */ import React, { Component } from 'react'; import PropTypes from 'prop-types'; import { View, TextInput, StyleSheet, Dimensions, Text, Animated } from 'react-native'; import zxcvbn from 'zxcvbn'; import _ from 'lodash'; const { width: wWidth } = Dimensions.get('window'); const widthByPercent = (percentage, containerWidth = wWidth) => { const value = (percentage * containerWidth) / 100; return Math.round(value); }; const regex = { digitsPattern: /\d/, lettersPattern: /[a-zA-Z]/, lowerCasePattern: /[a-z]/, upperCasePattern: /[A-Z]/, wordsPattern: /\w/, symbolsPattern: /\W/ }; export default class PasswordStrengthChecker extends Component { static defaultProps = { minLevel: 2, minLength: 0, ruleNames: 'lowerCase|upperCase|digits|symbols', strengthLevels: [ { label: 'Weak', labelColor: '#fff', widthPercent: 33, innerBarColor: '#fe6c6c' }, { label: 'Weak', labelColor: '#fff', widthPercent: 33, innerBarColor: '#fe6c6c' }, { label: 'Fair', labelColor: '#fff', widthPercent: 67, innerBarColor: '#feb466' }, { label: 'Fair', labelColor: '#fff', widthPercent: 67, innerBarColor: '#feb466' }, { label: 'Strong', labelColor: '#fff', widthPercent: 100, innerBarColor: '#6cfeb5' } ], tooShort: { enabled: false, labelColor: '#fff', label: 'Too short', widthPercent: 33, innerBarColor: '#fe6c6c' }, barColor: '#ffffff', barWidthPercent: 70, 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, inputStyle: TextInput.propTypes.style, strengthWrapperStyle: View.propTypes.style, strengthBarStyle: View.propTypes.style, innerStrengthBarStyle: View.propTypes.style, strengthDescriptionStyle: Text.propTypes.style, barColor: PropTypes.string, barWidthPercent: PropTypes.number, 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 } } 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 }).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) .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': return regex.symbolsPattern.test(password); case 'words': return regex.wordsPattern.test(password); case 'digits': return regex.digitsPattern.test(password); case 'letters': return regex.lettersPattern.test(password); case 'lowerCase': return regex.lowerCasePattern.test(password); case 'upperCase': return regex.upperCasePattern.test(password); default: return true; } } calculateScore(text) { if (!text) { this.setState({ isTooShort: false }); return -1; } if (this.isTooShort(text)) { this.setState({ isTooShort: true }); return 0; } this.setState({ 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 }); const isValid = this.isMatchingRules(password) && level >= this.props.minLevel; this.props.onChangeText(password, isValid); } renderPasswordInput() { const { inputWrapperStyle, inputStyle } = this.props; return ( <View style={[styles.inputWrapper, inputWrapperStyle]}> <TextInput selectionColor="#fff" autoCapitalize="none" autoCorrect={false} multiline={false} underlineColorAndroid="transparent" {...this.props} style={[styles.input, inputStyle]} onChangeText={text => this.onChangeText(text)} /> </View> ); } renderPasswordStrength() { const { barWidthPercent, tooShort, strengthLevels, barColor, strengthWrapperStyle, strengthBarStyle, innerStrengthBarStyle, strengthDescriptionStyle, showBarOnEmpty } = this.props; const barWidth = widthByPercent(barWidthPercent); const { level } = this.state; let strengthLevelBarStyle = {}, strengthLevelLabelStyle = {}, strengthLevelLabel = '', innerBarWidth = 0; if (level !== -1) { if (!showBarOnEmpty) { this.showFullBar(); } innerBarWidth = widthByPercent(strengthLevels[level].widthPercent, barWidth); strengthLevelBarStyle = { backgroundColor: strengthLevels[level].innerBarColor }; strengthLevelLabelStyle = { color: strengthLevels[level].labelColor }; strengthLevelLabel = strengthLevels[level].label; if (tooShort.enabled && this.state.isTooShort) { innerBarWidth = widthByPercent(tooShort.widthPercent, barWidth) || widthByPercent(strengthLevels[level].widthPercent, barWidth); strengthLevelBarStyle = { backgroundColor: tooShort.innerBarColor || strengthLevels[level].innerBarColor }; strengthLevelLabelStyle = { color: tooShort.labelColor || strengthLevels[level].labelColor }; strengthLevelLabel = tooShort.label || strengthLevels[level].label; } } else { if (!showBarOnEmpty) { this.showFullBar(false); } } Animated.timing(this.animatedInnerBarWidth, { toValue: innerBarWidth, 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> <Text style={[styles.strengthDescription, strengthDescriptionStyle, { ...strengthLevelLabelStyle }]}>{strengthLevelLabel}</Text> </View> ); } render() { return ( <View style={styles.wrapper}> {this.renderPasswordInput()} {this.renderPasswordStrength()} </View> ); } } const styles = StyleSheet.create({ wrapper: { backgroundColor: 'transparent', }, inputWrapper: { flex: 1, flexDirection: 'row', justifyContent: 'center', alignItems: 'center', backgroundColor: 'transparent', borderBottomWidth: 0.8, borderColor: 'rgba(242, 242, 242, 0.5)' }, input: { flex: 1, color: '#fff', paddingTop: 7, paddingBottom: 10, fontSize: 20 }, passwordStrengthWrapper: { flexDirection: 'row', flexWrap: 'wrap', marginBottom: 10 }, passwordStrengthBar: { height: 10, position: 'relative', top: 5, bottom: 5, borderRadius: 5 }, innerPasswordStrengthBar: { height: 10, borderRadius: 5, width: 0 }, strengthDescription: { color: '#fff', backgroundColor: 'transparent', textAlign: 'right', position: 'absolute', right: 5, top: 1, fontSize: 14 } });