Last active
February 21, 2018 10:20
-
-
Save kerbyfc/06075c049176712c23b7f5eb54876d77 to your computer and use it in GitHub Desktop.
Password strength validator
This file contains hidden or 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
| describe('immutableArrayMerge', () => { | |
| it('should merge simple arrays', () => { | |
| immutableArrayMerge([1, 3], [2, , 5]).should.be.eql([2, 3, 5]); | |
| immutableArrayMerge([1, 3], [2, undefined, 5]).should.be.eql([2, undefined, 5]); | |
| }); | |
| it('should merge arrays with objects', () => { | |
| immutableArrayMerge([{a: [{b: 2, c: [3]}]}], [{a: [{b: 3, c: [4, 5]}]}]).should.be.eql([{a: [{b: 3, c: [4, 5]}]}]); | |
| }); | |
| }); | |
| describe('immutableMerge', () => { | |
| it('should merge simple objects', () => { | |
| immutableMerge({a: 1}, {b: 2}).should.be.eql({a: 1, b: 2}); | |
| }); | |
| it('should merge objects with array values', () => { | |
| immutableMerge({a: [1]}, {a: [2]}).should.be.eql({a: [2]}); | |
| immutableMerge({a: [1, 2, 3, 4, 5, 6]}, {a: [, , , , 10]}).should.be.eql({a: [1, 2, 3, 4, 10, 6]}); | |
| }); | |
| it('should merge complex objects', () => { | |
| immutableMerge({f: [{a: [{b: 2, c: [3]}]}]}, {f: [{a: [{b: 3, c: [4, 5]}]}]}).should.be.eql({f: [{a: [{b: 3, c: [4, 5]}]}]}); | |
| }); | |
| it('should use customizer while merging objects', () => { | |
| immutableMerge({a: [1, 2, 3]}, {a: [4, 5, 6]}, { | |
| mergeArrays: ({sourceValue, targetValue}) => { | |
| return _.union(targetValue, sourceValue); | |
| }, | |
| }).should.be.eql({a: [1, 2, 3, 4, 5, 6]}); | |
| }); | |
| }); |
This file contains hidden or 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
| import * as _ from 'lodash'; | |
| interface IMergeCustomizerOptions<TType> { | |
| sourceValue: TType; | |
| targetValue: TType; | |
| key: string; | |
| source: object | any[]; | |
| target: object | any[]; | |
| } | |
| interface IMergeOptions { | |
| mergeObjects?(options: IMergeCustomizerOptions<object>): any; | |
| mergeArrays?(options: IMergeCustomizerOptions<any[]>): any; | |
| mergePrimitives?(options: IMergeCustomizerOptions<any>): any; | |
| } | |
| /** | |
| * @example immutableMerge(state, flat.unflatten({'path.to.state.array.properties.1': 1, 'path.to.state.prop': 2})); | |
| */ | |
| export function immutableMerge<TSrc extends {}, TMix extends {}>(source: TSrc, target: TMix, options: IMergeOptions = {}): TSrc & TMix { | |
| return Object.keys(target).reduce((acc, key) => { | |
| const targetValue: any = target[key]; | |
| let sourceValue: any = source[key]; | |
| const customizerOptions = {sourceValue, targetValue, key, source, target}; | |
| if (areObjects(sourceValue, targetValue)) { | |
| return { | |
| ...acc as object, | |
| [key]: !options.mergeObjects ? | |
| immutableMerge(sourceValue, targetValue, options) : options.mergeObjects(customizerOptions), | |
| }; | |
| } | |
| if (areArrays(sourceValue, targetValue)) { | |
| return { | |
| ...acc as object, | |
| [key]: !options.mergeArrays ? | |
| immutableArrayMerge(sourceValue, targetValue, options) : options.mergeArrays(customizerOptions), | |
| }; | |
| } | |
| return { | |
| ...acc as object, | |
| [key]: !options.mergePrimitives ? targetValue : options.mergePrimitives(customizerOptions), | |
| }; | |
| }, _.clone(source)) as TSrc & TMix; | |
| } | |
| export function immutableArrayMerge<TSrc extends any[]>(origin: any[], value: any[], options: IMergeOptions = {}): TSrc { | |
| let index; | |
| let result = [...origin]; | |
| for (index in value) { | |
| if (value.hasOwnProperty(index) && /^\d+$/.test(index)) { | |
| let replacement = value[index]; | |
| index = parseInt(index, 10); | |
| if (areObjects(result[index], value[index])) { | |
| replacement = immutableMerge(result[index], value[index], options); | |
| } | |
| if (areArrays(result[index], value[index])) { | |
| replacement = immutableArrayMerge(result[index], value[index], options); | |
| } | |
| result = [ | |
| ...result.slice(0, index), | |
| replacement, | |
| ...result.slice(index + 1), | |
| ]; | |
| } | |
| } | |
| return result as TSrc; | |
| } |
This file contains hidden or 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
| export type TPasswordValidatorRules = { | |
| length?: boolean; // min length | |
| digit?: boolean; // must contain digits | |
| twoCases?: boolean; // must contain different cases | |
| } | |
| export type TPasswordValidatorOptions = { | |
| length?: number; | |
| rules?: TPasswordValidatorRules; | |
| } | |
| const defaultOptions: TPasswordValidatorOptions = { | |
| length: 6, | |
| rules: { | |
| length: true, | |
| digit: true, | |
| twoCases: true | |
| } | |
| }; | |
| /** | |
| * Check passwords strength | |
| * | |
| * @example | |
| * const validator = new PasswordValidator(); // PasswordValidator {rules: Object} | |
| * validator.check("ы") // false | |
| * validator.getState() // Object {length: false, digit: false, twoCases: false} | |
| * validator.check("ы2") // false | |
| * validator.getState() // Object {length: false, twoCases: false} | |
| * validator.check("ы2qwff") // false | |
| * validator.getState(true) // Object {length: true, digit: true, twoCases: false} | |
| * validator.check("ы2qwffШШ") // true | |
| * validator.getState() // Object {} | |
| */ | |
| export class PasswordValidator implements IStringValidator { | |
| private rules: TPasswordValidatorRules = defaultOptions.rules; | |
| check(password: string, options: TPasswordValidatorOptions = {}): boolean { | |
| options = _.defaultsDeep(_.cloneDeep(options), defaultOptions); | |
| this.reset(); | |
| const rules: TPasswordValidatorRules = { | |
| length: !options.rules.length || this.checkLength(password, options.length), | |
| digit: !options.rules.digit || this.checkDigit(password), | |
| twoCases: !options.rules.twoCases || this.checkTwoCases(password) | |
| }; | |
| return _.every(rules, (isPassed) => isPassed); | |
| } | |
| getState(onlyErrors: boolean = true): TPasswordValidatorRules { | |
| if (onlyErrors) { | |
| return _.omitBy(this.rules, (isPassed: boolean) => isPassed); | |
| } | |
| return {...this.rules}; // safe | |
| } | |
| private reset(): void { | |
| this.rules = _.mapValues(this.rules, () => true); | |
| } | |
| private checkLength(password: string, length: number): boolean { | |
| if (password.length < length) { | |
| return this.rules.length = false; | |
| } | |
| return true; | |
| } | |
| private checkDigit(password: string): boolean { | |
| if (password === password.replace(/\d/g, '')) { | |
| return this.rules.digit = false; | |
| } | |
| return true; | |
| } | |
| private checkTwoCases(password: string): boolean { | |
| const doesContainAlphaSymbols = password.replace(/\d/g, '') === password.replace(/[^\d]/g, ''); | |
| const doesContainUppercasedLetters = password.toLowerCase() === password; | |
| return this.rules.twoCases = doesContainAlphaSymbols && doesContainUppercasedLetters; | |
| } | |
| } | |
| export default PasswordValidator; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment