module Pwned::PasswordBase

This class represents a password. It does all the work of talking to the Pwned Passwords API to find out if the password has been pwned. @see haveibeenpwned.com/API/v2#PwnedPasswords

Constants

API_URL

The base URL for the Pwned Passwords API

DEFAULT_REQUEST_HEADERS

The default request headers that are used to make HTTP requests to the API. A user agent is provided as requested in the documentation. @see haveibeenpwned.com/API/v2#UserAgent

HASH_PREFIX_LENGTH

The number of characters from the start of the hash of the password that are used to search for the range of passwords.

SHA1_LENGTH

The total length of a SHA1 hash

Attributes

hashed_password[R]

Returns the full SHA1 hash of the given password in uppercase. @return [String] The full SHA1 hash of the given password. @since 1.0.0

ignore_env_proxy[R]
request_headers[R]
request_options[R]
request_proxy[R]

Public Instance Methods

pwned?() click to toggle source

@example

password = Pwned::Password.new("password")
password.pwned? #=> true
password.pwned? #=> true

@return [Boolean] true when the password has been pwned. @raise [Pwned::Error] if there are errors with the HTTP request. @raise [Pwned::TimeoutError] if the HTTP request times out. @since 1.0.0

# File lib/pwned/password_base.rb, line 43
def pwned?
  pwned_count > 0
end
pwned_count() click to toggle source

@example

password = Pwned::Password.new("password")
password.pwned_count #=> 3303003

@return [Integer] the number of times the password has been pwned. @raise [Pwned::Error] if there are errors with the HTTP request. @raise [Pwned::TimeoutError] if the HTTP request times out. @since 1.0.0

# File lib/pwned/password_base.rb, line 56
def pwned_count
  @pwned_count ||= fetch_pwned_count
end

Private Instance Methods

fetch_pwned_count() click to toggle source
# File lib/pwned/password_base.rb, line 70
def fetch_pwned_count
  for_each_response_line do |line|
    next unless line.start_with?(hashed_password_suffix)
    # Count starts after the suffix, followed by a colon
    return line[(SHA1_LENGTH-HASH_PREFIX_LENGTH+1)..-1].to_i
  end

  # The hash was not found, we can assume the password is not pwned [yet]
  0
end
for_each_response_line(&block) click to toggle source
# File lib/pwned/password_base.rb, line 81
def for_each_response_line(&block)
  begin
    with_http_response "#{API_URL}#{hashed_password_prefix}" do |response|
      response.value # raise if request was unsuccessful
      stream_response_lines(response, &block)
    end
  rescue Timeout::Error => e
    raise Pwned::TimeoutError, e.message
  rescue => e
    raise Pwned::Error, e.message
  end
end
hashed_password_prefix() click to toggle source
# File lib/pwned/password_base.rb, line 94
def hashed_password_prefix
  @hashed_password[0...HASH_PREFIX_LENGTH]
end
hashed_password_suffix() click to toggle source
# File lib/pwned/password_base.rb, line 98
def hashed_password_suffix
  @hashed_password[HASH_PREFIX_LENGTH..-1]
end
stream_response_lines(response) { |last_line| ... } click to toggle source

Stream a Net::HTTPResponse by line, handling lines that cross chunks.

# File lib/pwned/password_base.rb, line 127
def stream_response_lines(response, &block)
  last_line = ""

  response.read_body do |chunk|
    chunk_lines = (last_line + chunk).lines
    # This could end with half a line, so save it for next time. If
    # chunk_lines is empty, pop returns nil, so this also ensures last_line
    # is always a string.
    last_line = chunk_lines.pop || ""
    chunk_lines.each(&block)
  end

  yield last_line unless last_line.empty?
end
with_http_response(url, &block) click to toggle source

Make a HTTP GET request given the url and headers. Yields a `Net::HTTPResponse`.

# File lib/pwned/password_base.rb, line 104
def with_http_response(url, &block)
  uri = URI(url)

  request = Net::HTTP::Get.new(uri)
  request.initialize_http_header(request_headers)
  request_options[:use_ssl] = true

  environment_proxy = ignore_env_proxy ? nil : :ENV

  Net::HTTP.start(
    uri.host,
    uri.port,
    request_proxy&.host || environment_proxy,
    request_proxy&.port,
    request_proxy&.user,
    request_proxy&.password,
    request_options
  ) do |http|
    http.request(request, &block)
  end
end