<commit_before># frozen_string_literal: true

require "net/http"

module Devise
  module Models
    # The PwnedPassword module adds a new validation for Devise Models.
    # No modifications to routes or controllers needed.
    # Simply add :pwned_password to the list of included modules in your
    # devise module, and all new registrations will be blocked if they use
    # a password in this dataset https://haveibeenpwned.com/Passwords.
    module PwnedPassword
      extend ActiveSupport::Concern

      included do
        validate :not_pwned_password
      end

      module ClassMethods
        Devise::Models.config(self, :min_password_matches)
        Devise::Models.config(self, :pwned_password_open_timeout)
        Devise::Models.config(self, :pwned_password_read_timeout)
      end

      private

        def usage_count(response, suffix)
          count = 0
          response.each_line do |line|
            if line.start_with? suffix
              count = line.strip.split(":").last.to_i
              break
            end
          end
          count
        end

        # Returns true if password is present in the PwnedPasswords dataset
        # Implement retry behaviour described here https://haveibeenpwned.com/API/v2#RateLimiting
        def password_pwned?(password)
          hash = Digest::SHA1.hexdigest(password).upcase
          prefix, suffix = hash.slice!(0..4), hash

          userAgent = "devise_pwned_password"

          uri = URI.parse("https://api.pwnedpasswords.com/range/#{prefix}")

          begin
            Net::HTTP.start(uri.host, uri.port, use_ssl: true, open_timeout: self.class.pwned_password_open_timeout, read_timeout: self.class.pwned_password_read_timeout) do |http|
              request = Net::HTTP::Get.new(uri.request_uri, "User-Agent" => userAgent)
              response = http.request request
              return false unless response.is_a?(Net::HTTPSuccess)
              return usage_count(response.read_body, suffix) >= self.class.min_password_matches
            end
          rescue StandardError
            return false
          end

          false
        end

        def not_pwned_password
          # This deliberately fails silently on 500's etc. Most apps wont want to tie the ability to sign up customers to the availability of a third party API
          if password_pwned?(password)
            # Error message taken from https://haveibeenpwned.com/Passwords
            errors.add(:password, "This password has previously appeared in a data breach and should never be used. Please choose something harder to guess.")
          end
        end
    end
  end
end
<commit_msg>Fix “no implicit conversion of nil into String”<commit_after># frozen_string_literal: true

require "net/http"

module Devise
  module Models
    # The PwnedPassword module adds a new validation for Devise Models.
    # No modifications to routes or controllers needed.
    # Simply add :pwned_password to the list of included modules in your
    # devise module, and all new registrations will be blocked if they use
    # a password in this dataset https://haveibeenpwned.com/Passwords.
    module PwnedPassword
      extend ActiveSupport::Concern

      included do
        validate :not_pwned_password, if: :password_required?
      end

      module ClassMethods
        Devise::Models.config(self, :min_password_matches)
        Devise::Models.config(self, :pwned_password_open_timeout)
        Devise::Models.config(self, :pwned_password_read_timeout)
      end

      private

        def usage_count(response, suffix)
          count = 0
          response.each_line do |line|
            if line.start_with? suffix
              count = line.strip.split(":").last.to_i
              break
            end
          end
          count
        end

        # Returns true if password is present in the PwnedPasswords dataset
        # Implement retry behaviour described here https://haveibeenpwned.com/API/v2#RateLimiting
        def password_pwned?(password)
          hash = Digest::SHA1.hexdigest(password).upcase
          prefix, suffix = hash.slice!(0..4), hash

          userAgent = "devise_pwned_password"

          uri = URI.parse("https://api.pwnedpasswords.com/range/#{prefix}")

          begin
            Net::HTTP.start(uri.host, uri.port, use_ssl: true, open_timeout: self.class.pwned_password_open_timeout, read_timeout: self.class.pwned_password_read_timeout) do |http|
              request = Net::HTTP::Get.new(uri.request_uri, "User-Agent" => userAgent)
              response = http.request request
              return false unless response.is_a?(Net::HTTPSuccess)
              return usage_count(response.read_body, suffix) >= self.class.min_password_matches
            end
          rescue StandardError
            return false
          end

          false
        end

        def not_pwned_password
          # This deliberately fails silently on 500's etc. Most apps wont want to tie the ability to sign up customers to the availability of a third party API
          if password_pwned?(password)
            # Error message taken from https://haveibeenpwned.com/Passwords
            errors.add(:password, "This password has previously appeared in a data breach and should never be used. Please choose something harder to guess.")
          end
        end
    end
  end
end

{'fraction_non_alphanumeric': 0.06984952628645737, 'fraction_numerical': 0.002972320267508824, 'mean_word_length': 2.4032869785082176, 'pattern_counts': {'":': 2, '<': 3, '<?xml version=': 0, '>': 7, 'https://': 8, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2251061', 'n_tokens_mistral': 1470, 'n_tokens_neox': 1439, 'n_words': 441}