pwned 1.2.1 → 2.0.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.
@@ -2,7 +2,7 @@
2
2
  <html>
3
3
  <head>
4
4
  <meta charset="utf-8">
5
- <title>Documentation by YARD 0.9.12</title>
5
+ <title>Documentation by YARD 0.9.20</title>
6
6
  </head>
7
7
  <script type="text/javascript" charset="utf-8">
8
8
  var match = unescape(window.location.hash).match(/^#!(.+)/);
@@ -6,7 +6,7 @@
6
6
  <title>
7
7
  File: README
8
8
 
9
- &mdash; Documentation by YARD 0.9.12
9
+ &mdash; Documentation by YARD 0.9.20
10
10
 
11
11
  </title>
12
12
 
@@ -74,6 +74,33 @@ src="https://inch-ci.org/github/philnash/pwned.svg?branch=master"></a></p>
74
74
  <p><a href="https://philnash.github.io/pwned/">API docs</a> | <a
75
75
  href="https://github.com/philnash/pwned">GitHub repo</a></p>
76
76
 
77
+ <h2 id="label-Table+of+Contents">Table of Contents</h2>
78
+ <ul><li>
79
+ <p><a href="#about">About</a></p>
80
+ </li><li>
81
+ <p><a href="#installation">Installation</a></p>
82
+ </li><li>
83
+ <p><a href="#usage">Usage</a></p>
84
+ </li><li>
85
+ <p><a href="#plain-ruby">Plain Ruby</a></p>
86
+ </li><li>
87
+ <p><a href="#activerecord-validator">Rails (ActiveRecord)</a></p>
88
+ </li><li>
89
+ <p><a href="#devise">Devise</a></p>
90
+ </li><li>
91
+ <p><a href="#command-line">Command line</a></p>
92
+ </li><li>
93
+ <p><a href="#how-pwned-is-pi">How Pwned is Pi?</a></p>
94
+ </li><li>
95
+ <p><a href="#development">Development</a></p>
96
+ </li><li>
97
+ <p><a href="#contributing">Contributing</a></p>
98
+ </li><li>
99
+ <p><a href="#license">License</a></p>
100
+ </li><li>
101
+ <p><a href="#code-of-conduct">Code of Conduct</a></p>
102
+ </li></ul>
103
+
77
104
  <h2 id="label-About">About</h2>
78
105
 
79
106
  <p>Troy Hunt&#39;s <a
@@ -93,6 +120,11 @@ API, please check <a
93
120
  href="https://haveibeenpwned.com/API/v2#AcceptableUse">the acceptable uses
94
121
  and license of the API</a>.</p>
95
122
 
123
+ <p>Here is a blog post I wrote on <a
124
+ href="https://www.twilio.com/blog/2018/03/better-passwords-in-ruby-applications-pwned-passwords-api.html">how
125
+ to use this gem in your Ruby applications to make your users’ passwords
126
+ better</a>.</p>
127
+
96
128
  <h2 id="label-Installation">Installation</h2>
97
129
 
98
130
  <p>Add this line to your application&#39;s Gemfile:</p>
@@ -112,6 +144,17 @@ and license of the API</a>.</p>
112
144
 
113
145
  <h2 id="label-Usage">Usage</h2>
114
146
 
147
+ <p>There are a few ways you can use this gem:</p>
148
+ <ol><li>
149
+ <p><a href="#plain-ruby">Plain Ruby</a></p>
150
+ </li><li>
151
+ <p><a href="#activerecord-validator">Rails</a></p>
152
+ </li><li>
153
+ <p><a href="#devise">Rails and Devise</a></p>
154
+ </li></ol>
155
+
156
+ <h3 id="label-Plain+Ruby">Plain Ruby</h3>
157
+
115
158
  <p>To test a password against the API, instantiate a
116
159
  <code>Pwned::Password</code> object and then ask if it is
117
160
  <code>pwned?</code>.</p>
@@ -154,12 +197,14 @@ been pwned, or how many times it was pwned:</p>
154
197
 
155
198
  <h4 id="label-Advanced">Advanced</h4>
156
199
 
157
- <p>You can set options and headers to be used with <code>open-uri</code> when
158
- making the request to the API. HTTP headers must be string keys and the <a
159
- href="https://ruby-doc.org/stdlib-2.5.0/libdoc/open-uri/rdoc/OpenURI/OpenRead.html#method-i-open">other
160
- options are available in the OpenURI::OpenRead module</a>.</p>
200
+ <p>You can set http request options to be used with
201
+ <code>Net::HTTP.start</code> when making the request to the API. These
202
+ options are documented in the <a
203
+ href="http://ruby-doc.org/stdlib-2.6.3/libdoc/net/http/rdoc/Net/HTTP.html#method-c-start">Net::HTTP.start
204
+ documentation</a>. The <code>:headers</code> option defines defines HTTP
205
+ headers. These headers must be string keys.</p>
161
206
 
162
- <pre class="code ruby"><code class="ruby"><span class='id identifier rubyid_password'>password</span> <span class='op'>=</span> <span class='const'><span class='object_link'><a href="Pwned.html" title="Pwned (module)">Pwned</a></span></span><span class='op'>::</span><span class='const'><span class='object_link'><a href="Pwned/Password.html" title="Pwned::Password (class)">Password</a></span></span><span class='period'>.</span><span class='id identifier rubyid_new'><span class='object_link'><a href="Pwned/Password.html#initialize-instance_method" title="Pwned::Password#initialize (method)">new</a></span></span><span class='lparen'>(</span><span class='tstring'><span class='tstring_beg'>&quot;</span><span class='tstring_content'>password</span><span class='tstring_end'>&quot;</span></span><span class='comma'>,</span> <span class='lbrace'>{</span> <span class='tstring'><span class='tstring_beg'>&#39;</span><span class='tstring_content'>User-Agent</span><span class='tstring_end'>&#39;</span></span> <span class='op'>=&gt;</span> <span class='tstring'><span class='tstring_beg'>&#39;</span><span class='tstring_content'>Super fun new user agent</span><span class='tstring_end'>&#39;</span></span> <span class='rbrace'>}</span><span class='rparen'>)</span>
207
+ <pre class="code ruby"><code class="ruby"><span class='id identifier rubyid_password'>password</span> <span class='op'>=</span> <span class='const'><span class='object_link'><a href="Pwned.html" title="Pwned (module)">Pwned</a></span></span><span class='op'>::</span><span class='const'><span class='object_link'><a href="Pwned/Password.html" title="Pwned::Password (class)">Password</a></span></span><span class='period'>.</span><span class='id identifier rubyid_new'><span class='object_link'><a href="Pwned/Password.html#initialize-instance_method" title="Pwned::Password#initialize (method)">new</a></span></span><span class='lparen'>(</span><span class='tstring'><span class='tstring_beg'>&quot;</span><span class='tstring_content'>password</span><span class='tstring_end'>&quot;</span></span><span class='comma'>,</span> <span class='label'>headers:</span> <span class='lbrace'>{</span> <span class='tstring'><span class='tstring_beg'>&#39;</span><span class='tstring_content'>User-Agent</span><span class='tstring_end'>&#39;</span></span> <span class='op'>=&gt;</span> <span class='tstring'><span class='tstring_beg'>&#39;</span><span class='tstring_content'>Super fun new user agent</span><span class='tstring_end'>&#39;</span></span> <span class='rbrace'>}</span><span class='comma'>,</span> <span class='label'>read_timeout:</span> <span class='int'>10</span><span class='rparen'>)</span>
163
208
  </code></pre>
164
209
 
165
210
  <h3 id="label-ActiveRecord+Validator">ActiveRecord Validator</h3>
@@ -198,7 +243,7 @@ whether the <code>pwned_count</code> is greater than the threshold.</p>
198
243
  <span class='kw'>end</span>
199
244
  </code></pre>
200
245
 
201
- <h4 id="label-Network+Errors+Handling">Network Errors Handling</h4>
246
+ <h4 id="label-Network+Error+Handling">Network Error Handling</h4>
202
247
 
203
248
  <p>By default the record will be treated as valid when we cannot reach the <a
204
249
  href="https://haveibeenpwned.com/">haveibeenpwned.com</a> servers. This can
@@ -233,19 +278,104 @@ be changed with the <code>:on_error</code> validator parameter:</p>
233
278
 
234
279
  <p>You can configure network requests made from the validator using
235
280
  <code>:request_options</code> (see <a
236
- href="http://ruby-doc.org/stdlib-2.5.0/libdoc/open-uri/rdoc/OpenURI/OpenRead.html#method-i-open">OpenURI::OpenRead#open</a>
237
- for the list of available options, string keys represent custom network
238
- request headers, e.g. <code>&quot;User-Agent&quot;</code>):</p>
281
+ href="http://ruby-doc.org/stdlib-2.6.3/libdoc/net/http/rdoc/Net/HTTP.html#method-c-start">Net::HTTP.start</a>
282
+ for the list of available options). In addition to these options, HTTP
283
+ headers can be specified with the <code>:headers</code> key, e.g.
284
+ <code>&quot;User-Agent&quot;</code>):</p>
239
285
 
240
286
  <pre class="code ruby"><code class="ruby"><span class='id identifier rubyid_validates'>validates</span> <span class='symbol'>:password</span><span class='comma'>,</span> <span class='label'>not_pwned:</span> <span class='lbrace'>{</span>
241
- <span class='label'>request_options:</span> <span class='lbrace'>{</span> <span class='label'>read_timeout:</span> <span class='int'>5</span><span class='comma'>,</span> <span class='label'>open_timeout:</span> <span class='int'>1</span><span class='comma'>,</span> <span class='tstring'><span class='tstring_beg'>&quot;</span><span class='tstring_content'>User-Agent</span><span class='tstring_end'>&quot;</span></span> <span class='op'>=&gt;</span> <span class='tstring'><span class='tstring_beg'>&quot;</span><span class='tstring_content'>Super fun user agent</span><span class='tstring_end'>&quot;</span></span> <span class='rbrace'>}</span>
287
+ <span class='label'>request_options:</span> <span class='lbrace'>{</span> <span class='label'>read_timeout:</span> <span class='int'>5</span><span class='comma'>,</span> <span class='label'>open_timeout:</span> <span class='int'>1</span><span class='comma'>,</span> <span class='label'>headers:</span> <span class='lbrace'>{</span> <span class='tstring'><span class='tstring_beg'>&quot;</span><span class='tstring_content'>User-Agent</span><span class='tstring_end'>&quot;</span></span> <span class='op'>=&gt;</span> <span class='tstring'><span class='tstring_beg'>&quot;</span><span class='tstring_content'>Super fun user agent</span><span class='tstring_end'>&quot;</span></span> <span class='rbrace'>}</span> <span class='rbrace'>}</span>
242
288
  <span class='rbrace'>}</span>
243
289
  </code></pre>
244
290
 
245
- <h2 id="label-TODO">TODO</h2>
246
- <ul><li>
247
- <p>[ ] Devise plugin</p>
248
- </li></ul>
291
+ <h3 id="label-Devise">Devise</h3>
292
+
293
+ <p>If you are using Devise I recommend you use the <a
294
+ href="https://github.com/michaelbanfield/devise-pwned_password">devise-pwned_password
295
+ extension</a> which is now powered by this gem.</p>
296
+
297
+ <h3 id="label-Command+line">Command line</h3>
298
+
299
+ <p>The gem provides a command line utility for checking passwords. You can
300
+ call it from your terminal application like this:</p>
301
+
302
+ <pre class="code ruby"><code class="ruby">$ pwned password
303
+ Pwned!
304
+ The password has been found in public breaches 3645804 times.
305
+ </code></pre>
306
+
307
+ <p>If you don&#39;t want the password you are checking to be visible, call:</p>
308
+
309
+ <pre class="code ruby"><code class="ruby">$ pwned --secret
310
+ </code></pre>
311
+
312
+ <p>You will be prompted for the password, but it won&#39;t be displayed.</p>
313
+
314
+ <h2 id="label-How+Pwned+is+Pi-3F">How Pwned is Pi?</h2>
315
+
316
+ <p><a href="https://github.com/daz">@daz</a> <a
317
+ href="https://twitter.com/dazonic/status/1074647842046660609">shared</a> a
318
+ fantastic example of using this gem to show how many times the digits of Pi
319
+ have been used as passwords and leaked.</p>
320
+
321
+ <pre class="code ruby"><code class="ruby"><span class='id identifier rubyid_require'>require</span> <span class='tstring'><span class='tstring_beg'>&#39;</span><span class='tstring_content'>pwned</span><span class='tstring_end'>&#39;</span></span>
322
+
323
+ <span class='const'>PI</span> <span class='op'>=</span> <span class='tstring'><span class='tstring_beg'>&#39;</span><span class='tstring_content'>3.14159265358979323846264338327950288419716939937510582097494459230781640628620899862803482534211706798214808651328230664709384460955058223172535940812848111</span><span class='tstring_end'>&#39;</span></span>
324
+
325
+ <span class='kw'>for</span> <span class='id identifier rubyid_n'>n</span> <span class='kw'>in</span> <span class='int'>1</span><span class='op'>..</span><span class='int'>40</span>
326
+ <span class='id identifier rubyid_password'>password</span> <span class='op'>=</span> <span class='const'><span class='object_link'><a href="Pwned.html" title="Pwned (module)">Pwned</a></span></span><span class='op'>::</span><span class='const'><span class='object_link'><a href="Pwned/Password.html" title="Pwned::Password (class)">Password</a></span></span><span class='period'>.</span><span class='id identifier rubyid_new'><span class='object_link'><a href="Pwned/Password.html#initialize-instance_method" title="Pwned::Password#initialize (method)">new</a></span></span> <span class='const'>PI</span><span class='lbracket'>[</span><span class='int'>0</span><span class='op'>..</span><span class='lparen'>(</span><span class='id identifier rubyid_n'>n</span> <span class='op'>+</span> <span class='int'>1</span><span class='rparen'>)</span><span class='rbracket'>]</span>
327
+ <span class='id identifier rubyid_str'>str</span> <span class='op'>=</span> <span class='lbracket'>[</span> <span class='id identifier rubyid_n'>n</span><span class='period'>.</span><span class='id identifier rubyid_to_s'>to_s</span><span class='period'>.</span><span class='id identifier rubyid_rjust'>rjust</span><span class='lparen'>(</span><span class='int'>2</span><span class='rparen'>)</span> <span class='rbracket'>]</span>
328
+ <span class='id identifier rubyid_str'>str</span> <span class='op'>&lt;&lt;</span> <span class='lparen'>(</span><span class='id identifier rubyid_password'>password</span><span class='period'>.</span><span class='id identifier rubyid_pwned?'>pwned?</span> <span class='op'>?</span> <span class='tstring'><span class='tstring_beg'>&#39;</span><span class='tstring_content'>😡</span><span class='tstring_end'>&#39;</span></span> <span class='op'>:</span> <span class='tstring'><span class='tstring_beg'>&#39;</span><span class='tstring_content'>😃</span><span class='tstring_end'>&#39;</span></span><span class='rparen'>)</span>
329
+ <span class='id identifier rubyid_str'>str</span> <span class='op'>&lt;&lt;</span> <span class='id identifier rubyid_password'>password</span><span class='period'>.</span><span class='id identifier rubyid_pwned_count'>pwned_count</span><span class='period'>.</span><span class='id identifier rubyid_to_s'>to_s</span><span class='period'>.</span><span class='id identifier rubyid_rjust'>rjust</span><span class='lparen'>(</span><span class='int'>4</span><span class='rparen'>)</span>
330
+ <span class='id identifier rubyid_str'>str</span> <span class='op'>&lt;&lt;</span> <span class='id identifier rubyid_password'>password</span><span class='period'>.</span><span class='id identifier rubyid_password'>password</span>
331
+
332
+ <span class='id identifier rubyid_puts'>puts</span> <span class='id identifier rubyid_str'>str</span><span class='period'>.</span><span class='id identifier rubyid_join'>join</span> <span class='tstring'><span class='tstring_beg'>&#39;</span><span class='tstring_content'> </span><span class='tstring_end'>&#39;</span></span>
333
+ <span class='kw'>end</span>
334
+ </code></pre>
335
+
336
+ <p>The results may, or may not, surprise you.</p>
337
+
338
+ <pre class="code ruby"><code class="ruby">1 😡 16 3.1
339
+ 2 😡 238 3.14
340
+ 3 😡 34 3.141
341
+ 4 😡 1345 3.1415
342
+ 5 😡 2552 3.14159
343
+ 6 😡 791 3.141592
344
+ 7 😡 9582 3.1415926
345
+ 8 😡 1591 3.14159265
346
+ 9 😡 637 3.141592653
347
+ 10 😡 873 3.1415926535
348
+ 11 😡 137 3.14159265358
349
+ 12 😡 103 3.141592653589
350
+ 13 😡 65 3.1415926535897
351
+ 14 😡 201 3.14159265358979
352
+ 15 😡 41 3.141592653589793
353
+ 16 😡 57 3.1415926535897932
354
+ 17 😡 28 3.14159265358979323
355
+ 18 😡 29 3.141592653589793238
356
+ 19 😡 1 3.1415926535897932384
357
+ 20 😡 7 3.14159265358979323846
358
+ 21 😡 5 3.141592653589793238462
359
+ 22 😡 2 3.1415926535897932384626
360
+ 23 😡 2 3.14159265358979323846264
361
+ 24 😃 0 3.141592653589793238462643
362
+ 25 😡 3 3.1415926535897932384626433
363
+ 26 😃 0 3.14159265358979323846264338
364
+ 27 😃 0 3.141592653589793238462643383
365
+ 28 😃 0 3.1415926535897932384626433832
366
+ 29 😃 0 3.14159265358979323846264338327
367
+ 30 😃 0 3.141592653589793238462643383279
368
+ 31 😃 0 3.1415926535897932384626433832795
369
+ 32 😃 0 3.14159265358979323846264338327950
370
+ 33 😃 0 3.141592653589793238462643383279502
371
+ 34 😃 0 3.1415926535897932384626433832795028
372
+ 35 😃 0 3.14159265358979323846264338327950288
373
+ 36 😃 0 3.141592653589793238462643383279502884
374
+ 37 😃 0 3.1415926535897932384626433832795028841
375
+ 38 😃 0 3.14159265358979323846264338327950288419
376
+ 39 😃 0 3.141592653589793238462643383279502884197
377
+ 40 😃 0 3.1415926535897932384626433832795028841971
378
+ </code></pre>
249
379
 
250
380
  <h2 id="label-Development">Development</h2>
251
381
 
@@ -284,9 +414,9 @@ of conduct</a>.</p>
284
414
  </div></div>
285
415
 
286
416
  <div id="footer">
287
- Generated on Sat Mar 17 09:15:05 2018 by
417
+ Generated on Tue Oct 1 21:19:36 2019 by
288
418
  <a href="http://yardoc.org" title="Yay! A Ruby Documentation Tool" target="_parent">yard</a>
289
- 0.9.12 (ruby-2.5.0).
419
+ 0.9.20 (ruby-2.5.5).
290
420
  </div>
291
421
 
292
422
  </div>
@@ -120,6 +120,49 @@ function summaryToggle() {
120
120
  } else { localStorage.summaryCollapsed = "expand"; }
121
121
  }
122
122
 
123
+ function constantSummaryToggle() {
124
+ $('.constants_summary_toggle').click(function(e) {
125
+ e.preventDefault();
126
+ localStorage.summaryCollapsed = $(this).text();
127
+ $('.constants_summary_toggle').each(function() {
128
+ $(this).text($(this).text() == "collapse" ? "expand" : "collapse");
129
+ var next = $(this).parent().parent().nextAll('dl.constants').first();
130
+ if (next.hasClass('compact')) {
131
+ next.toggle();
132
+ next.nextAll('dl.constants').first().toggle();
133
+ }
134
+ else if (next.hasClass('constants')) {
135
+ var list = $('<dl class="constants compact" />');
136
+ list.html(next.html());
137
+ list.find('dt').each(function() {
138
+ $(this).addClass('summary_signature');
139
+ $(this).text( $(this).text().split('=')[0]);
140
+ if ($(this).has(".deprecated").length) {
141
+ $(this).addClass('deprecated');
142
+ };
143
+ });
144
+ // Add the value of the constant as "Tooltip" to the summary object
145
+ list.find('pre.code').each(function() {
146
+ console.log($(this).parent());
147
+ var dt_element = $(this).parent().prev();
148
+ var tooltip = $(this).text();
149
+ if (dt_element.hasClass("deprecated")) {
150
+ tooltip = 'Deprecated. ' + tooltip;
151
+ };
152
+ dt_element.attr('title', tooltip);
153
+ });
154
+ list.find('.docstring, .tags, dd').remove();
155
+ next.before(list);
156
+ next.toggle();
157
+ }
158
+ });
159
+ return false;
160
+ });
161
+ if (localStorage.summaryCollapsed == "collapse") {
162
+ $('.constants_summary_toggle').first().click();
163
+ } else { localStorage.summaryCollapsed = "expand"; }
164
+ }
165
+
123
166
  function generateTOC() {
124
167
  if ($('#filecontents').length === 0) return;
125
168
  var _toc = $('<ol class="top"></ol>');
@@ -232,6 +275,16 @@ function mainFocus() {
232
275
  setTimeout(function() { $('#main').focus(); }, 10);
233
276
  }
234
277
 
278
+ function navigationChange() {
279
+ // This works around the broken anchor navigation with the YARD template.
280
+ window.onpopstate = function() {
281
+ var hash = window.location.hash;
282
+ if (hash !== '' && $(hash)[0]) {
283
+ $(hash)[0].scrollIntoView();
284
+ }
285
+ };
286
+ }
287
+
235
288
  $(document).ready(function() {
236
289
  navResizer();
237
290
  navExpander();
@@ -241,8 +294,10 @@ $(document).ready(function() {
241
294
  searchFrameButtons();
242
295
  linkSummaries();
243
296
  summaryToggle();
297
+ constantSummaryToggle();
244
298
  generateTOC();
245
299
  mainFocus();
300
+ navigationChange();
246
301
  });
247
302
 
248
303
  })();
@@ -6,7 +6,7 @@
6
6
  <title>
7
7
  Top Level Namespace
8
8
 
9
- &mdash; Documentation by YARD 0.9.12
9
+ &mdash; Documentation by YARD 0.9.20
10
10
 
11
11
  </title>
12
12
 
@@ -102,9 +102,9 @@
102
102
  </div>
103
103
 
104
104
  <div id="footer">
105
- Generated on Sat Mar 17 09:15:05 2018 by
105
+ Generated on Tue Oct 1 21:19:37 2019 by
106
106
  <a href="http://yardoc.org" title="Yay! A Ruby Documentation Tool" target="_parent">yard</a>
107
- 0.9.12 (ruby-2.5.0).
107
+ 0.9.20 (ruby-2.5.5).
108
108
  </div>
109
109
 
110
110
  </div>
@@ -29,10 +29,10 @@ module Pwned
29
29
  # Pwned.pwned?("pwned::password") #=> false
30
30
  #
31
31
  # @param password [String] The password you want to check against the API.
32
- # @param [Hash] request_options Options that can be passed to +open+ when
32
+ # @param [Hash] request_options Options that can be passed to +Net::HTTP.start+ when
33
33
  # calling the API
34
- # @option request_options [String] 'User-Agent' ("Ruby Pwned::Password #{Pwned::VERSION}")
35
- # The user agent used when making an API request.
34
+ # @option request_options [Symbol] :headers ({ "User-Agent" => '"Ruby Pwned::Password #{Pwned::VERSION}" })
35
+ # HTTP headers to include in the request
36
36
  # @return [Boolean] Whether the password appears in the data breaches or not.
37
37
  # @since 1.1.0
38
38
  def self.pwned?(password, request_options={})
@@ -47,10 +47,10 @@ module Pwned
47
47
  # Pwned.pwned_count("pwned::password") #=> 0
48
48
  #
49
49
  # @param password [String] The password you want to check against the API.
50
- # @param [Hash] request_options Options that can be passed to +open+ when
50
+ # @param [Hash] request_options Options that can be passed to +Net::HTTP.start+ when
51
51
  # calling the API
52
- # @option request_options [String] 'User-Agent' ("Ruby Pwned::Password #{Pwned::VERSION}")
53
- # The user agent used when making an API request.
52
+ # @option request_options [Symbol] :headers ({ "User-Agent" => '"Ruby Pwned::Password #{Pwned::VERSION}" })
53
+ # HTTP headers to include in the request
54
54
  # @return [Integer] The number of times the password has appeared in the data
55
55
  # breaches.
56
56
  # @since 1.1.0
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "digest"
4
- require "open-uri"
4
+ require 'net/http'
5
5
 
6
6
  module Pwned
7
7
  ##
@@ -23,10 +23,10 @@ module Pwned
23
23
  SHA1_LENGTH = 40
24
24
 
25
25
  ##
26
- # The default request options that are used to make HTTP requests to the
26
+ # The default request headers that are used to make HTTP requests to the
27
27
  # API. A user agent is provided as requested in the documentation.
28
28
  # @see https://haveibeenpwned.com/API/v2#UserAgent
29
- DEFAULT_REQUEST_OPTIONS = {
29
+ DEFAULT_REQUEST_HEADERS = {
30
30
  "User-Agent" => "Ruby Pwned::Password #{Pwned::VERSION}"
31
31
  }.freeze
32
32
 
@@ -40,21 +40,23 @@ module Pwned
40
40
  #
41
41
  # @example A simple password with the default request options
42
42
  # password = Pwned::Password.new("password")
43
- # @example Setting the user agent and the read timeout of the reques
44
- # password = Pwned::Password.new("password", "User-Agent" => "My user agent", :read_timout => 10)
43
+ # @example Setting the user agent and the read timeout of the request
44
+ # password = Pwned::Password.new("password", headers: { "User-Agent" => "My user agent" }, read_timout: 10)
45
45
  #
46
46
  # @param password [String] The password you want to check against the API.
47
- # @param [Hash] request_options Options that can be passed to +open+ when
47
+ # @param [Hash] request_options Options that can be passed to +Net::HTTP.start+ when
48
48
  # calling the API
49
- # @option request_options [String] 'User-Agent' ("Ruby Pwned::Password #{Pwned::VERSION}")
50
- # The user agent used when making an API request.
49
+ # @option request_options [Symbol] :headers ({ "User-Agent" => '"Ruby Pwned::Password #{Pwned::VERSION}" })
50
+ # HTTP headers to include in the request
51
51
  # @return [Boolean] Whether the password appears in the data breaches or not.
52
52
  # @raise [TypeError] if the password is not a string.
53
53
  # @since 1.1.0
54
54
  def initialize(password, request_options={})
55
55
  raise TypeError, "password must be of type String" unless password.is_a? String
56
56
  @password = password
57
- @request_options = DEFAULT_REQUEST_OPTIONS.merge(request_options)
57
+ @request_options = Hash(request_options).dup
58
+ @request_headers = Hash(request_options.delete(:headers))
59
+ @request_headers = DEFAULT_REQUEST_HEADERS.merge(@request_headers)
58
60
  end
59
61
 
60
62
  ##
@@ -93,6 +95,8 @@ module Pwned
93
95
 
94
96
  private
95
97
 
98
+ attr_reader :request_options, :request_headers
99
+
96
100
  def fetch_pwned_count
97
101
  for_each_response_line do |line|
98
102
  next unless line.start_with?(hashed_password_suffix)
@@ -106,8 +110,9 @@ module Pwned
106
110
 
107
111
  def for_each_response_line(&block)
108
112
  begin
109
- open("#{API_URL}#{hashed_password_prefix}", @request_options) do |io|
110
- io.each_line(&block)
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)
111
116
  end
112
117
  rescue Timeout::Error => e
113
118
  raise Pwned::TimeoutError, e.message
@@ -123,5 +128,33 @@ module Pwned
123
128
  def hashed_password_suffix
124
129
  hashed_password[HASH_PREFIX_LENGTH..-1]
125
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
+
126
159
  end
127
160
  end