# frozen_string_literal: true class ReeValidator::ValidatePassword include Ree::FnDSL fn :validate_password do link :validate_length link :t, from: :ree_i18n end DEFAULTS = { min_length: 1, digit_count: 0, lowercase_char_count: 0, uppercase_char_count: 0, special_symbol_count: 0 } PasswordErr = Class.new(StandardError) doc(<<~DOC) Validates password with selected rules: - min length - max length - min lowercase character - min uppercase character - min special symbol count - min digit count Examples of usage: validate_password('Password1!') #=> true validate_password('Password1!, digit_count: 3) #=> false validate_password('Pass1!, lowercase_chars_count: 3) #=> true validate_password('Password1!, uppercase_chars_count: 3) #=> false validate_password('Password1!$%, special_symbols_count: 3) #=> true DOC contract( String, Nilor[StandardError], Ksplat[ min_length?: Integer, max_length?: Integer, digit_count?: Integer, lowercase_char_count?: Integer, uppercase_char_count?: Integer, special_symbol_count?: Integer ] => Bool ) def call(password, error = nil, **opts) opts = DEFAULTS.merge(opts) if opts[:max_length] validate_length( password, error || PasswordErr.new( t( "validator.password.max_password_length", {number: opts[:max_length]}, default_by_locale: :en ) ), max: opts[:max_length] ) end validate_length( password, error || PasswordErr.new( t( "validator.password.min_password_length", {number: opts[:min_length]}, default_by_locale: :en ) ), min: opts[:min_length] ) if !password.match?(/\A(?=.*\d{#{opts[:digit_count]},})/x) error ||= PasswordErr.new( t( "validator.password.wrong_number_of_digits", {number: opts[:digit_count]}, default_by_locale: :en ) ) raise error end if !password.match?(/\A(?=.*[a-z]{#{opts[:lowercase_char_count]},})/x) error ||= PasswordErr.new( t( "validator.password.wrong_number_of_lowercase_chars", {number: opts[:lowercase_char_count]}, default_by_locale: :en ) ) raise error end if !password.match?(/\A(?=.*[A-Z]{#{opts[:uppercase_char_count]},})/x) error ||= PasswordErr.new( t( "validator.password.wrong_number_of_uppercase_chars", {number: opts[:uppercase_char_count]}, default_by_locale: :en ) ) raise error end if !password.match?(/\A(?=.*[[:^alnum:]]{#{opts[:special_symbol_count]},})/x) error ||= PasswordErr.new( t( "validator.password.wrong_number_of_special_symbols", {number: opts[:special_symbol_count]}, default_by_locale: :en ) ) raise error end true end end