56 lines
1.3 KiB
Ruby
56 lines
1.3 KiB
Ruby
# Criteri "di mercato" (stile Cognito/Auth0 bilanciato):
|
|
# - minimo 8 caratteri (max 72 per bcrypt)
|
|
# - almeno 3 classi su 4: minuscole, maiuscole, numeri, simboli
|
|
module PasswordComplexity
|
|
extend ActiveSupport::Concern
|
|
|
|
MIN_LENGTH = 8
|
|
MAX_LENGTH = 72
|
|
REQUIRED_CLASSES = 3
|
|
|
|
CLASS_CHECKS = {
|
|
lowercase: /[a-z]/,
|
|
uppercase: /[A-Z]/,
|
|
digit: /\d/,
|
|
symbol: /[^A-Za-z0-9]/
|
|
}.freeze
|
|
|
|
class << self
|
|
def violation(password)
|
|
value = password.to_s
|
|
return :blank if value.blank?
|
|
return :too_short if value.length < MIN_LENGTH
|
|
return :too_long if value.bytesize > MAX_LENGTH
|
|
return :too_weak unless strong_enough?(value)
|
|
|
|
nil
|
|
end
|
|
|
|
def strong_enough?(password)
|
|
matched = CLASS_CHECKS.count { |_, pattern| password.match?(pattern) }
|
|
matched >= REQUIRED_CLASSES
|
|
end
|
|
|
|
def requirement_summary
|
|
I18n.t("password_policy.hint")
|
|
end
|
|
end
|
|
|
|
included do
|
|
validate :password_meets_complexity_policy, if: -> { password.present? }
|
|
end
|
|
|
|
private
|
|
|
|
def password_meets_complexity_policy
|
|
case PasswordComplexity.violation(password)
|
|
when :too_short
|
|
errors.add(:password, :too_short, count: MIN_LENGTH)
|
|
when :too_long
|
|
errors.add(:password, :too_long, count: MAX_LENGTH)
|
|
when :too_weak
|
|
errors.add(:password, :complexity)
|
|
end
|
|
end
|
|
end
|