pwned 2.0.1 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/FUNDING.yml +1 -0
- data/.github/workflows/tests.yml +39 -0
- data/CHANGELOG.md +33 -2
- data/README.md +127 -11
- data/bin/pwned +3 -3
- data/lib/pwned/hashed_password.rb +40 -0
- data/lib/pwned/password.rb +9 -123
- data/lib/pwned/password_base.rb +142 -0
- data/lib/pwned/version.rb +1 -1
- data/lib/pwned.rb +24 -2
- data/pwned.gemspec +2 -2
- metadata +13 -31
- data/.travis.yml +0 -27
- data/docs/NotPwnedValidator.html +0 -494
- data/docs/Pwned/Error.html +0 -149
- data/docs/Pwned/Password.html +0 -936
- data/docs/Pwned/TimeoutError.html +0 -152
- data/docs/Pwned.html +0 -521
- data/docs/PwnedValidator.html +0 -192
- data/docs/_index.html +0 -162
- data/docs/class_list.html +0 -51
- data/docs/css/common.css +0 -1
- data/docs/css/full_list.css +0 -58
- data/docs/css/style.css +0 -496
- data/docs/file.README.html +0 -424
- data/docs/file_list.html +0 -56
- data/docs/frames.html +0 -17
- data/docs/index.html +0 -424
- data/docs/js/app.js +0 -303
- data/docs/js/full_list.js +0 -216
- data/docs/js/jquery.js +0 -4
- data/docs/method_list.html +0 -115
- data/docs/top-level-namespace.html +0 -112
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f4b8270eaf162b50ef112371c2e35dd41141dec39e4e11d5a76936119e2ca569
|
4
|
+
data.tar.gz: fdec9b67cc6465fa64062697253e6cf078ddb2b2deb71cf829a919dca7953f48
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7ec757852674e3e44ac71a71ed5c31d3503b5f0547871f940d56e8e2d8838b0b89e36742c0e10108f1c00fddc54016454d3ba77348cddd7be659d1ed4fdaf71a
|
7
|
+
data.tar.gz: 304b59ce60639f57c7a3a81c5e0f172dc8de9a2128018abe636be7f6d88074af59ac193d2538f86c298d4f8a537b0152bdcab6efb8cf4a27d5c6a13c64b9e311
|
data/.github/FUNDING.yml
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
github: philnash
|
@@ -0,0 +1,39 @@
|
|
1
|
+
name: tests
|
2
|
+
|
3
|
+
on: [push, pull_request]
|
4
|
+
|
5
|
+
jobs:
|
6
|
+
test:
|
7
|
+
runs-on: ubuntu-latest
|
8
|
+
strategy:
|
9
|
+
fail-fast: false
|
10
|
+
matrix:
|
11
|
+
ruby: [2.5, 2.6, 2.7, 3.0, head]
|
12
|
+
rails: [4.2.11.3, 5.0.7.2, 5.1.7, 5.2.4.4, 6.0.3.4, 6.1.0]
|
13
|
+
exclude:
|
14
|
+
# Ruby 3.0 and Rails 5 do not get along together.
|
15
|
+
- ruby: 3.0
|
16
|
+
rails: 5.0.7.2
|
17
|
+
- ruby: 3.0
|
18
|
+
rails: 5.1.7
|
19
|
+
- ruby: 3.0
|
20
|
+
rails: 5.2.4.4
|
21
|
+
- ruby: head
|
22
|
+
rails: 5.0.7.2
|
23
|
+
- ruby: head
|
24
|
+
rails: 5.1.7
|
25
|
+
- ruby: head
|
26
|
+
rails: 5.2.4.4
|
27
|
+
continue-on-error: ${{ endsWith(matrix.ruby, 'head') }}
|
28
|
+
env:
|
29
|
+
RAILS_VERSION: ${{ matrix.rails }}
|
30
|
+
steps:
|
31
|
+
- uses: actions/checkout@v2
|
32
|
+
- name: Set up Ruby ${{ matrix.ruby }}
|
33
|
+
uses: ruby/setup-ruby@v1
|
34
|
+
with:
|
35
|
+
ruby-version: ${{ matrix.ruby }}
|
36
|
+
- name: "Install dependencies (rails: ${{matrix.rails}})"
|
37
|
+
run: bundle install
|
38
|
+
- name: Run tests
|
39
|
+
run: bundle exec rspec
|
data/CHANGELOG.md
CHANGED
@@ -1,8 +1,39 @@
|
|
1
1
|
# Changelog for `Pwned`
|
2
2
|
|
3
|
-
## Ongoing [☰](https://github.com/philnash/pwned/compare/v2.0
|
3
|
+
## Ongoing [☰](https://github.com/philnash/pwned/compare/v2.2.0...master)
|
4
4
|
|
5
|
-
## 2.0
|
5
|
+
## 2.3.0 (August 30, 2021) [☰](https://github.com/philnash/pwned/compare/v2.2.0...v2.3.0)
|
6
|
+
|
7
|
+
- Minor updates
|
8
|
+
|
9
|
+
- Restores `Net::HTTP` default behaviour to use environment supplied HTTP
|
10
|
+
proxy
|
11
|
+
- Adds `ignore_env_proxy` to ignore any proxies set in the environment
|
12
|
+
|
13
|
+
## 2.2.0 (March 27, 2021) [☰](https://github.com/philnash/pwned/compare/v2.1.0...v2.2.0)
|
14
|
+
|
15
|
+
- Minor updates
|
16
|
+
|
17
|
+
- Adds `:proxy` option to `request_options` to directly set a proxy on the
|
18
|
+
request. Fixes #21, thanks [dparpyani](https://github.com/dparpyani).
|
19
|
+
|
20
|
+
## 2.1.0 (July 8, 2020) [☰](https://github.com/philnash/pwned/compare/v2.0.2...v2.1.0)
|
21
|
+
|
22
|
+
- Minor updates
|
23
|
+
|
24
|
+
- Adds `Pwned::HashedPassword` class which is initializd with a SHA1 hash to
|
25
|
+
query the API with so that the lookup can be done in the background without
|
26
|
+
storing passwords. Fixes #19, thanks [@paprikati](https://github.com/paprikati).
|
27
|
+
|
28
|
+
## 2.0.2 (May 20, 2020) [☰](https://github.com/philnash/pwned/compare/v2.0.1...v2.0.2)
|
29
|
+
|
30
|
+
- Minor fix
|
31
|
+
|
32
|
+
- It was found to be possible for reading the lines body of a response to
|
33
|
+
result in a `nil` which caused trouble with string concatenation. This
|
34
|
+
avoids that scenario. Fixes #18, thanks [@flori](https://github.com/flori).
|
35
|
+
|
36
|
+
## 2.0.1 (January 14, 2020) [☰](https://github.com/philnash/pwned/compare/v2.0.0...v2.0.1)
|
6
37
|
|
7
38
|
- Minor updates
|
8
39
|
|
data/README.md
CHANGED
@@ -2,19 +2,32 @@
|
|
2
2
|
|
3
3
|
An easy, Ruby way to use the Pwned Passwords API.
|
4
4
|
|
5
|
-
[](https://rubygems.org/gems/pwned)
|
5
|
+
[](https://rubygems.org/gems/pwned)  [](https://codeclimate.com/github/philnash/pwned/maintainability) [](https://inch-ci.org/github/philnash/pwned)
|
6
6
|
|
7
|
-
[API docs](https://
|
7
|
+
[API docs](https://www.rubydoc.info/gems/pwned) | [GitHub repo](https://github.com/philnash/pwned)
|
8
8
|
|
9
9
|
## Table of Contents
|
10
10
|
|
11
|
+
* [Table of Contents](#table-of-contents)
|
11
12
|
* [About](#about)
|
12
13
|
* [Installation](#installation)
|
13
14
|
* [Usage](#usage)
|
14
15
|
* [Plain Ruby](#plain-ruby)
|
15
|
-
|
16
|
+
* [Custom request options](#custom-request-options)
|
17
|
+
* [HTTP Headers](#http-headers)
|
18
|
+
* [HTTP Proxy](#http-proxy)
|
19
|
+
* [ActiveRecord Validator](#activerecord-validator)
|
20
|
+
* [I18n](#i18n)
|
21
|
+
* [Threshold](#threshold)
|
22
|
+
* [Network Error Handling](#network-error-handling)
|
23
|
+
* [Custom Request Options](#custom-request-options-1)
|
24
|
+
* [HTTP Headers](#http-headers-1)
|
25
|
+
* [HTTP Proxy](#http-proxy-1)
|
26
|
+
* [Using Asynchronously](#using-asynchronously)
|
16
27
|
* [Devise](#devise)
|
28
|
+
* [Rodauth](#rodauth)
|
17
29
|
* [Command line](#command-line)
|
30
|
+
* [Unpwn](#unpwn)
|
18
31
|
* [How Pwned is Pi?](#how-pwned-is-pi)
|
19
32
|
* [Development](#development)
|
20
33
|
* [Contributing](#contributing)
|
@@ -95,13 +108,49 @@ Pwned.pwned_count("password")
|
|
95
108
|
#=> 3303003
|
96
109
|
```
|
97
110
|
|
98
|
-
####
|
111
|
+
#### Custom request options
|
99
112
|
|
100
|
-
You can set http request options to be used with `Net::HTTP.start` when making the request to the API. These options are
|
101
|
-
documented in the [`Net::HTTP.start` documentation](http://ruby-doc.org/stdlib-2.6.3/libdoc/net/http/rdoc/Net/HTTP.html#method-c-start). The `:headers` option defines defines HTTP headers. These headers must be string keys.
|
113
|
+
You can set http request options to be used with `Net::HTTP.start` when making the request to the API. These options are documented in the [`Net::HTTP.start` documentation](https://ruby-doc.org/stdlib-3.0.0/libdoc/net/http/rdoc/Net/HTTP.html#method-c-start). For example:
|
102
114
|
|
103
115
|
```ruby
|
104
|
-
password = Pwned::Password.new("password",
|
116
|
+
password = Pwned::Password.new("password", read_timeout: 10)
|
117
|
+
```
|
118
|
+
|
119
|
+
##### HTTP Headers
|
120
|
+
|
121
|
+
The `:headers` option defines defines HTTP headers. These headers must be string keys.
|
122
|
+
|
123
|
+
```ruby
|
124
|
+
password = Pwned::Password.new("password", headers: {
|
125
|
+
'User-Agent' => 'Super fun new user agent'
|
126
|
+
})
|
127
|
+
```
|
128
|
+
|
129
|
+
##### HTTP Proxy
|
130
|
+
|
131
|
+
An HTTP proxy can be set using the `http_proxy` or `HTTP_PROXY` environment variable. This is the same way that `Net::HTTP` handles HTTP proxies if no proxy options are given. See [`URI::Generic#find_proxy`](https://ruby-doc.org/stdlib-3.0.1/libdoc/uri/rdoc/URI/Generic.html#method-i-find_proxy) for full details on how Ruby detects a proxy from the environment.
|
132
|
+
|
133
|
+
```ruby
|
134
|
+
# Set in the environment
|
135
|
+
ENV["http_proxy"] = "https://username:password@example.com:12345"
|
136
|
+
|
137
|
+
# Will use the above proxy
|
138
|
+
password = Pwned::Password.new("password")
|
139
|
+
```
|
140
|
+
|
141
|
+
You can specify a custom HTTP proxy with the `:proxy` option:
|
142
|
+
|
143
|
+
```ruby
|
144
|
+
password = Pwned::Password.new(
|
145
|
+
"password",
|
146
|
+
proxy: "https://username:password@example.com:12345"
|
147
|
+
)
|
148
|
+
```
|
149
|
+
|
150
|
+
If you don't want to set a proxy and you don't want a proxy to be inferred from the environment, set the `:ignore_env_proxy` key:
|
151
|
+
|
152
|
+
```ruby
|
153
|
+
password = Pwned::Password.new("password", ignore_env_proxy: true)
|
105
154
|
```
|
106
155
|
|
107
156
|
### ActiveRecord Validator
|
@@ -171,18 +220,81 @@ end
|
|
171
220
|
|
172
221
|
#### Custom Request Options
|
173
222
|
|
174
|
-
You can configure network requests made from the validator using `:request_options` (see [Net::HTTP.start](http://ruby-doc.org/stdlib-2.6.3/libdoc/net/http/rdoc/Net/HTTP.html#method-c-start) for the list of available options).
|
175
|
-
|
223
|
+
You can configure network requests made from the validator using `:request_options` (see [Net::HTTP.start](http://ruby-doc.org/stdlib-2.6.3/libdoc/net/http/rdoc/Net/HTTP.html#method-c-start) for the list of available options).
|
224
|
+
|
225
|
+
```ruby
|
226
|
+
validates :password, not_pwned: {
|
227
|
+
request_options: {
|
228
|
+
read_timeout: 5,
|
229
|
+
open_timeout: 1
|
230
|
+
}
|
231
|
+
}
|
232
|
+
```
|
233
|
+
|
234
|
+
In addition to these options, you can also set the following:
|
235
|
+
|
236
|
+
##### HTTP Headers
|
237
|
+
|
238
|
+
HTTP headers can be specified with the `:headers` key (e.g. `"User-Agent"`)
|
176
239
|
|
177
240
|
```ruby
|
178
241
|
validates :password, not_pwned: {
|
179
|
-
request_options: {
|
242
|
+
request_options: {
|
243
|
+
headers: { "User-Agent" => "Super fun user agent" }
|
244
|
+
}
|
180
245
|
}
|
181
246
|
```
|
182
247
|
|
248
|
+
##### HTTP Proxy
|
249
|
+
|
250
|
+
An HTTP proxy can be set using the `http_proxy` or `HTTP_PROXY` environment variable. This is the same way that `Net::HTTP` handles HTTP proxies if no proxy options are given. See [`URI::Generic#find_proxy`](https://ruby-doc.org/stdlib-3.0.1/libdoc/uri/rdoc/URI/Generic.html#method-i-find_proxy) for full details on how Ruby detects a proxy from the environment.
|
251
|
+
|
252
|
+
```ruby
|
253
|
+
# Set in the environment
|
254
|
+
ENV["http_proxy"] = "https://username:password@example.com:12345"
|
255
|
+
|
256
|
+
validates :password, not_pwned: true
|
257
|
+
```
|
258
|
+
|
259
|
+
You can specify a custom HTTP proxy with the `:proxy` key:
|
260
|
+
|
261
|
+
```ruby
|
262
|
+
validates :password, not_pwned: {
|
263
|
+
request_options: {
|
264
|
+
proxy: "https://username:password@example.com:12345"
|
265
|
+
}
|
266
|
+
}
|
267
|
+
```
|
268
|
+
|
269
|
+
If you don't want to set a proxy and you don't want a proxy to be inferred from the environment, set the `:ignore_env_proxy` key:
|
270
|
+
|
271
|
+
```ruby
|
272
|
+
validates :password, not_pwned: {
|
273
|
+
request_options: {
|
274
|
+
ignore_env_proxy: true
|
275
|
+
}
|
276
|
+
}
|
277
|
+
```
|
278
|
+
|
279
|
+
### Using Asynchronously
|
280
|
+
|
281
|
+
You may have a use case for hashing the password in advance, and then making the call to the Pwned Passwords API later (for example if you want to enqueue a job without storing the plaintext password). To do this, you can hash the password with the `Pwned.hash_password` method and then initialize the `Pwned::HashPassword` class with the hash, like this:
|
282
|
+
|
283
|
+
```ruby
|
284
|
+
hashed_password = Pwned.hash_password(password)
|
285
|
+
# some time later
|
286
|
+
Pwned::HashPassword.new(hashed_password, request_options).pwned?
|
287
|
+
```
|
288
|
+
|
289
|
+
The `Pwned::HashPassword` constructor takes all the same options as the regular `Pwned::Password` contructor.
|
290
|
+
|
183
291
|
### Devise
|
184
292
|
|
185
|
-
If you are using Devise I recommend you use the [devise-pwned_password extension](https://github.com/michaelbanfield/devise-pwned_password) which is now powered by this gem.
|
293
|
+
If you are using [Devise](https://github.com/heartcombo/devise) I recommend you use the [devise-pwned_password extension](https://github.com/michaelbanfield/devise-pwned_password) which is now powered by this gem.
|
294
|
+
|
295
|
+
### Rodauth
|
296
|
+
|
297
|
+
If you are using [Rodauth](https://github.com/jeremyevans/rodauth) then you can use the [rodauth-pwned](https://github.com/janko/rodauth-pwned) feature which is powered by this gem.
|
186
298
|
|
187
299
|
### Command line
|
188
300
|
|
@@ -202,6 +314,10 @@ $ pwned --secret
|
|
202
314
|
|
203
315
|
You will be prompted for the password, but it won't be displayed.
|
204
316
|
|
317
|
+
### Unpwn
|
318
|
+
|
319
|
+
To cut down on unnecessary network requests, [the unpwn project](https://github.com/indirect/unpwn) uses a list of the top one million passwords to check passwords against. Only if a password is not included in the top million is it then checked against the Pwned Passwords API.
|
320
|
+
|
205
321
|
## How Pwned is Pi?
|
206
322
|
|
207
323
|
[@daz](https://github.com/daz) [shared](https://twitter.com/dazonic/status/1074647842046660609) a fantastic example of using this gem to show how many times the digits of Pi have been used as passwords and leaked.
|
data/bin/pwned
CHANGED
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "pwned/password_base"
|
4
|
+
|
5
|
+
module Pwned
|
6
|
+
##
|
7
|
+
# This class represents a hashed password. It does all the work of talking to the
|
8
|
+
# Pwned Passwords API to find out if the password has been pwned.
|
9
|
+
# @see https://haveibeenpwned.com/API/v2#PwnedPasswords
|
10
|
+
class HashedPassword
|
11
|
+
include PasswordBase
|
12
|
+
##
|
13
|
+
# Creates a new hashed password object.
|
14
|
+
#
|
15
|
+
# @example A simple password with the default request options
|
16
|
+
# password = Pwned::HashedPassword.new("ABC123")
|
17
|
+
# @example Setting the user agent and the read timeout of the request
|
18
|
+
# password = Pwned::HashedPassword.new("ABC123", headers: { "User-Agent" => "My user agent" }, read_timout: 10)
|
19
|
+
#
|
20
|
+
# @param hashed_password [String] The hash of the password you want to check against the API.
|
21
|
+
# @param [Hash] request_options Options that can be passed to +Net::HTTP.start+ when
|
22
|
+
# calling the API
|
23
|
+
# @option request_options [Symbol] :headers ({ "User-Agent" => "Ruby Pwned::Password #{Pwned::VERSION}" })
|
24
|
+
# HTTP headers to include in the request
|
25
|
+
# @option request_options [Symbol] :ignore_env_proxy (false) The library
|
26
|
+
# will try to infer an HTTP proxy from the `http_proxy` environment
|
27
|
+
# variable. If you do not want this behaviour, set this option to true.
|
28
|
+
# @raise [TypeError] if the password is not a string.
|
29
|
+
# @since 2.1.0
|
30
|
+
def initialize(hashed_password, request_options={})
|
31
|
+
raise TypeError, "hashed_password must be of type String" unless hashed_password.is_a? String
|
32
|
+
@hashed_password = hashed_password.upcase
|
33
|
+
@request_options = Hash(request_options).dup
|
34
|
+
@request_headers = Hash(request_options.delete(:headers))
|
35
|
+
@request_headers = DEFAULT_REQUEST_HEADERS.merge(@request_headers)
|
36
|
+
@request_proxy = URI(request_options.delete(:proxy)) if request_options.key?(:proxy)
|
37
|
+
@ignore_env_proxy = request_options.delete(:ignore_env_proxy) || false
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
data/lib/pwned/password.rb
CHANGED
@@ -1,7 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "
|
4
|
-
require 'net/http'
|
3
|
+
require "pwned/password_base"
|
5
4
|
|
6
5
|
module Pwned
|
7
6
|
##
|
@@ -9,27 +8,7 @@ module Pwned
|
|
9
8
|
# Pwned Passwords API to find out if the password has been pwned.
|
10
9
|
# @see https://haveibeenpwned.com/API/v2#PwnedPasswords
|
11
10
|
class Password
|
12
|
-
|
13
|
-
# The base URL for the Pwned Passwords API
|
14
|
-
API_URL = "https://api.pwnedpasswords.com/range/"
|
15
|
-
|
16
|
-
##
|
17
|
-
# The number of characters from the start of the hash of the password that
|
18
|
-
# are used to search for the range of passwords.
|
19
|
-
HASH_PREFIX_LENGTH = 5
|
20
|
-
|
21
|
-
##
|
22
|
-
# The total length of a SHA1 hash
|
23
|
-
SHA1_LENGTH = 40
|
24
|
-
|
25
|
-
##
|
26
|
-
# The default request headers that are used to make HTTP requests to the
|
27
|
-
# API. A user agent is provided as requested in the documentation.
|
28
|
-
# @see https://haveibeenpwned.com/API/v2#UserAgent
|
29
|
-
DEFAULT_REQUEST_HEADERS = {
|
30
|
-
"User-Agent" => "Ruby Pwned::Password #{Pwned::VERSION}"
|
31
|
-
}.freeze
|
32
|
-
|
11
|
+
include PasswordBase
|
33
12
|
##
|
34
13
|
# @return [String] the password that is being checked.
|
35
14
|
# @since 1.0.0
|
@@ -46,115 +25,22 @@ module Pwned
|
|
46
25
|
# @param password [String] The password you want to check against the API.
|
47
26
|
# @param [Hash] request_options Options that can be passed to +Net::HTTP.start+ when
|
48
27
|
# calling the API
|
49
|
-
# @option request_options [Symbol] :headers ({ "User-Agent" =>
|
28
|
+
# @option request_options [Symbol] :headers ({ "User-Agent" => "Ruby Pwned::Password #{Pwned::VERSION}" })
|
50
29
|
# HTTP headers to include in the request
|
51
|
-
# @
|
30
|
+
# @option request_options [Symbol] :ignore_env_proxy (false) The library
|
31
|
+
# will try to infer an HTTP proxy from the `http_proxy` environment
|
32
|
+
# variable. If you do not want this behaviour, set this option to true.
|
52
33
|
# @raise [TypeError] if the password is not a string.
|
53
34
|
# @since 1.1.0
|
54
35
|
def initialize(password, request_options={})
|
55
36
|
raise TypeError, "password must be of type String" unless password.is_a? String
|
56
37
|
@password = password
|
38
|
+
@hashed_password = Pwned.hash_password(password)
|
57
39
|
@request_options = Hash(request_options).dup
|
58
40
|
@request_headers = Hash(request_options.delete(:headers))
|
59
41
|
@request_headers = DEFAULT_REQUEST_HEADERS.merge(@request_headers)
|
42
|
+
@request_proxy = URI(request_options.delete(:proxy)) if request_options.key?(:proxy)
|
43
|
+
@ignore_env_proxy = request_options.delete(:ignore_env_proxy) || false
|
60
44
|
end
|
61
|
-
|
62
|
-
##
|
63
|
-
# Returns the full SHA1 hash of the given password in uppercase.
|
64
|
-
# @return [String] The full SHA1 hash of the given password.
|
65
|
-
# @since 1.0.0
|
66
|
-
def hashed_password
|
67
|
-
@hashed_password ||= Digest::SHA1.hexdigest(password).upcase
|
68
|
-
end
|
69
|
-
|
70
|
-
##
|
71
|
-
# @example
|
72
|
-
# password = Pwned::Password.new("password")
|
73
|
-
# password.pwned? #=> true
|
74
|
-
#
|
75
|
-
# @return [Boolean] +true+ when the password has been pwned.
|
76
|
-
# @raise [Pwned::Error] if there are errors with the HTTP request.
|
77
|
-
# @raise [Pwned::TimeoutError] if the HTTP request times out.
|
78
|
-
# @since 1.0.0
|
79
|
-
def pwned?
|
80
|
-
pwned_count > 0
|
81
|
-
end
|
82
|
-
|
83
|
-
##
|
84
|
-
# @example
|
85
|
-
# password = Pwned::Password.new("password")
|
86
|
-
# password.pwned_count #=> 3303003
|
87
|
-
#
|
88
|
-
# @return [Integer] the number of times the password has been pwned.
|
89
|
-
# @raise [Pwned::Error] if there are errors with the HTTP request.
|
90
|
-
# @raise [Pwned::TimeoutError] if the HTTP request times out.
|
91
|
-
# @since 1.0.0
|
92
|
-
def pwned_count
|
93
|
-
@pwned_count ||= fetch_pwned_count
|
94
|
-
end
|
95
|
-
|
96
|
-
private
|
97
|
-
|
98
|
-
attr_reader :request_options, :request_headers
|
99
|
-
|
100
|
-
def fetch_pwned_count
|
101
|
-
for_each_response_line do |line|
|
102
|
-
next unless line.start_with?(hashed_password_suffix)
|
103
|
-
# Count starts after the suffix, followed by a colon
|
104
|
-
return line[(SHA1_LENGTH-HASH_PREFIX_LENGTH+1)..-1].to_i
|
105
|
-
end
|
106
|
-
|
107
|
-
# The hash was not found, we can assume the password is not pwned [yet]
|
108
|
-
0
|
109
|
-
end
|
110
|
-
|
111
|
-
def for_each_response_line(&block)
|
112
|
-
begin
|
113
|
-
with_http_response "#{API_URL}#{hashed_password_prefix}" do |response|
|
114
|
-
response.value # raise if request was unsuccessful
|
115
|
-
stream_response_lines(response, &block)
|
116
|
-
end
|
117
|
-
rescue Timeout::Error => e
|
118
|
-
raise Pwned::TimeoutError, e.message
|
119
|
-
rescue => e
|
120
|
-
raise Pwned::Error, e.message
|
121
|
-
end
|
122
|
-
end
|
123
|
-
|
124
|
-
def hashed_password_prefix
|
125
|
-
hashed_password[0...HASH_PREFIX_LENGTH]
|
126
|
-
end
|
127
|
-
|
128
|
-
def hashed_password_suffix
|
129
|
-
hashed_password[HASH_PREFIX_LENGTH..-1]
|
130
|
-
end
|
131
|
-
|
132
|
-
# Make a HTTP GET request given the url and headers.
|
133
|
-
# Yields a `Net::HTTPResponse`.
|
134
|
-
def with_http_response(url, &block)
|
135
|
-
uri = URI(url)
|
136
|
-
|
137
|
-
request = Net::HTTP::Get.new(uri)
|
138
|
-
request.initialize_http_header(request_headers)
|
139
|
-
request_options[:use_ssl] = true
|
140
|
-
|
141
|
-
Net::HTTP.start(uri.host, uri.port, request_options) do |http|
|
142
|
-
http.request(request, &block)
|
143
|
-
end
|
144
|
-
end
|
145
|
-
|
146
|
-
# Stream a Net::HTTPResponse by line, handling lines that cross chunks.
|
147
|
-
def stream_response_lines(response, &block)
|
148
|
-
last_line = ''
|
149
|
-
|
150
|
-
response.read_body do |chunk|
|
151
|
-
chunk_lines = (last_line + chunk).lines
|
152
|
-
# This could end with half a line, so save it for next time
|
153
|
-
last_line = chunk_lines.pop
|
154
|
-
chunk_lines.each(&block)
|
155
|
-
end
|
156
|
-
yield last_line
|
157
|
-
end
|
158
|
-
|
159
45
|
end
|
160
46
|
end
|