Class: PassForge::BreachChecker
- Inherits:
-
Object
- Object
- PassForge::BreachChecker
- Defined in:
- lib/passforge/breach_checker.rb
Overview
Breach checker using HaveIBeenPwned API Uses k-anonymity to preserve privacy (only sends first 5 chars of hash)
Constant Summary collapse
- API_URL =
"https://api.pwnedpasswords.com/range/"
Class Method Summary collapse
-
.check(password) ⇒ Hash
Check if password has been breached.
-
.parse_response(response, suffix) ⇒ Object
Parse API response to find suffix match.
-
.query_api(prefix) ⇒ Object
Query HaveIBeenPwned API.
Class Method Details
.check(password) ⇒ Hash
Check if password has been breached
23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
# File 'lib/passforge/breach_checker.rb', line 23 def self.check(password) raise ArgumentError, "Password cannot be empty" if password.nil? || password.empty? # Generate SHA-1 hash hash = Digest::SHA1.hexdigest(password).upcase prefix = hash[0..4] suffix = hash[5..-1] # Query API with prefix only (k-anonymity) response = query_api(prefix) return { breached: false, count: 0 } if response.nil? # Check if our suffix appears in the response count = parse_response(response, suffix) { breached: count > 0, count: count } rescue StandardError => e # Return safe default on error { breached: nil, count: 0, error: e. } end |
.parse_response(response, suffix) ⇒ Object
Parse API response to find suffix match
74 75 76 77 78 79 80 81 |
# File 'lib/passforge/breach_checker.rb', line 74 def self.parse_response(response, suffix) response.each_line do |line| hash_suffix, count = line.strip.split(":") return count.to_i if hash_suffix == suffix end 0 end |
.query_api(prefix) ⇒ Object
Query HaveIBeenPwned API
54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 |
# File 'lib/passforge/breach_checker.rb', line 54 def self.query_api(prefix) uri = URI("#{API_URL}#{prefix}") http = Net::HTTP.new(uri.host, uri.port) http.use_ssl = true http.open_timeout = 5 http.read_timeout = 5 request = Net::HTTP::Get.new(uri) request["User-Agent"] = "PassForge-RubyGem" response = http.request(request) return nil unless response.is_a?(Net::HTTPSuccess) response.body end |