devise-pwned_password 0.1.1 → 0.1.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 4d25ddf45c7eddaf3e5334a1fc1dde9dfd0c1299
4
- data.tar.gz: 105d467a316fd317e76abb0226b0debecac123cc
3
+ metadata.gz: 9ef262f2244c9c92bd96259982b7fdcdf9b69118
4
+ data.tar.gz: c409c259481057b56aea0773b49cbe4b192a8847
5
5
  SHA512:
6
- metadata.gz: cdad317bb782d686f9f0734dd5d519b0d1b0329e89d272466fe57ea9737d890b58f8a2e99951fa1c55af34088697ee0a0bb54796e9eb60c646048fbb7f8821dc
7
- data.tar.gz: 005647a67db66a184ca7c53280c1743b985b15ca3a4968700de3c7e8aa549767d4880462219d7c02624f09887d58a299956f088c9399584fe9808ccf80b3f9d1
6
+ metadata.gz: 927ccc527b90c8b04e84e4a0d727a83a06ac97b715390c80a12cd511fd9d3645be3aec4ddc7dcc8c2507503a0a9cef60ae9871cd5a4d408727bcc30eb41d7760
7
+ data.tar.gz: 4eafd2132c7ceb6727e0efd5d059ceb3640988749e11fc471490f4c97efb8bfea4951b2b04dada6c1734e3d1dc7e54790c647d78838fbea0c91df92880fb1e24
data/README.md CHANGED
@@ -16,13 +16,24 @@ class AdminUser < ApplicationRecord
16
16
  end
17
17
  ```
18
18
 
19
-
20
- Users will receive the following error message if they use a password from the PwnedPasswords dataset
19
+ Users will receive the following error message if they use a password from the
20
+ PwnedPasswords dataset:
21
21
 
22
22
  ```
23
23
  This password has previously appeared in a data breach and should never be used. Please choose something harder to guess.
24
24
  ```
25
25
 
26
+ By default passwords are rejected if they appear at all in the data set.
27
+ Optionally, you can add the following snippet to `config/initializers/devise.rb`
28
+ if you want the error message to be displayed only when the password is present
29
+ a certain number of times in the data set:
30
+
31
+ ```ruby
32
+ # Minimum number of times a pwned password must exist in the data set in order
33
+ # to be reject.
34
+ config.min_password_matches = 10
35
+ ```
36
+
26
37
  ## Installation
27
38
  Add this line to your application's Gemfile:
28
39
 
@@ -35,6 +46,22 @@ And then execute:
35
46
  $ bundle install
36
47
  ```
37
48
 
49
+
50
+ ## Considerations
51
+
52
+ A few things to consider/understand when using this gem:
53
+
54
+ * User passwords are hashed using SHA-1 and then truncated to 5 characters,
55
+ implementing the k-Anonymity model described in
56
+ https://haveibeenpwned.com/API/v2#SearchingPwnedPasswordsByRange
57
+ Neither the clear-text password nor the full password hash is ever transmitted
58
+ to a third party. More implementation details and important caveats can be
59
+ found in https://blog.cloudflare.com/validating-leaked-passwords-with-k-anonymity/
60
+
61
+ * This puts an external API in the request path of users signing up to your
62
+ application. This could potentially add some latency to this operation. The
63
+ gem is designed to fail silently if the PwnedPasswords service is unavailable.
64
+
38
65
  ## Contributing
39
66
 
40
67
  To contribute
data/Rakefile CHANGED
@@ -1,17 +1,20 @@
1
+ # frozen_string_literal: true
2
+
1
3
  begin
2
- require 'bundler/setup'
4
+ require "bundler/setup"
5
+ require 'bundler/gem_tasks'
3
6
  rescue LoadError
4
- puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
7
+ puts "You must `gem install bundler` and `bundle install` to run rake tasks"
5
8
  end
6
9
 
7
- require 'rdoc/task'
10
+ require "rdoc/task"
8
11
 
9
12
  RDoc::Task.new(:rdoc) do |rdoc|
10
- rdoc.rdoc_dir = 'rdoc'
11
- rdoc.title = 'Devise::PwnedPassword'
12
- rdoc.options << '--line-numbers'
13
- rdoc.rdoc_files.include('README.md')
14
- rdoc.rdoc_files.include('lib/**/*.rb')
13
+ rdoc.rdoc_dir = "rdoc"
14
+ rdoc.title = "Devise::PwnedPassword"
15
+ rdoc.options << "--line-numbers"
16
+ rdoc.rdoc_files.include("README.md")
17
+ rdoc.rdoc_files.include("lib/**/*.rb")
15
18
  end
16
19
 
17
20
 
@@ -19,13 +22,13 @@ end
19
22
 
20
23
 
21
24
 
22
- require 'bundler/gem_tasks'
25
+ require "bundler/gem_tasks"
23
26
 
24
- require 'rake/testtask'
27
+ require "rake/testtask"
25
28
 
26
29
  Rake::TestTask.new(:test) do |t|
27
- t.libs << 'test'
28
- t.pattern = 'test/**/*_test.rb'
30
+ t.libs << "test"
31
+ t.pattern = "test/**/*_test.rb"
29
32
  t.verbose = false
30
33
  end
31
34
 
@@ -1,4 +1,6 @@
1
- require 'net/http'
1
+ # frozen_string_literal: true
2
+
3
+ require "net/http"
2
4
 
3
5
  module Devise
4
6
  module Models
@@ -14,49 +16,49 @@ module Devise
14
16
  validate :not_pwned_password
15
17
  end
16
18
 
17
- private
19
+ module ClassMethods
20
+ Devise::Models.config(self, :min_password_matches)
21
+ end
18
22
 
23
+ private
19
24
 
20
- # Returns true if password is present in the PwnedPasswords dataset
21
- # Implement retry behaviour described here https://haveibeenpwned.com/API/v2#RateLimiting
22
- def is_password_pwned(password)
25
+ def usage_count(response, suffix)
26
+ count = 0
27
+ response.each_line do |line|
28
+ if line.start_with? suffix
29
+ count = line.strip.split(":").last.to_i
30
+ break
31
+ end
32
+ end
33
+ count
34
+ end
23
35
 
24
- sha1Hash = Digest::SHA1.hexdigest password
36
+ # Returns true if password is present in the PwnedPasswords dataset
37
+ # Implement retry behaviour described here https://haveibeenpwned.com/API/v2#RateLimiting
38
+ def password_pwned?(password)
39
+ hash = Digest::SHA1.hexdigest(password).upcase
40
+ prefix, suffix = hash.slice!(0..4), hash
25
41
 
26
- userAgent = "devise_pwned_password"
42
+ userAgent = "devise_pwned_password"
27
43
 
28
- uri = URI.parse("https://haveibeenpwned.com/api/v2/pwnedpassword/#{sha1Hash}")
44
+ uri = URI.parse("https://api.pwnedpasswords.com/range/#{prefix}")
29
45
 
30
- Net::HTTP.start(uri.host, uri.port, :use_ssl => true) do |http|
31
- request = Net::HTTP::Get.new(uri.request_uri, {'User-Agent' => userAgent})
32
- 3.times {
46
+ Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http|
47
+ request = Net::HTTP::Get.new(uri.request_uri, "User-Agent" => userAgent)
33
48
  response = http.request request
34
- if response.code != '429'
35
- return response.code == '200'
36
- end
49
+ return usage_count(response.read_body, suffix) >= self.class.min_password_matches
50
+ end
37
51
 
38
- retryAfter = response.get_fields('Retry-After')[0].to_i
39
-
40
- if retryAfter > 10
41
- #Exit early if the throttling is too high
42
- return false
43
- end
44
-
45
- sleep retryAfter
46
- }
52
+ false
47
53
  end
48
54
 
49
- return false
50
- end
51
-
52
- def not_pwned_password
53
-
54
- #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
55
- if is_password_pwned(password)
56
- # Error message taken from https://haveibeenpwned.com/Passwords
57
- errors.add(:password, "This password has previously appeared in a data breach and should never be used. Please choose something harder to guess.")
55
+ def not_pwned_password
56
+ # 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
57
+ if password_pwned?(password)
58
+ # Error message taken from https://haveibeenpwned.com/Passwords
59
+ errors.add(:password, "This password has previously appeared in a data breach and should never be used. Please choose something harder to guess.")
60
+ end
58
61
  end
59
- end
60
62
  end
61
63
  end
62
- end
64
+ end
@@ -1,5 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Devise
2
4
  module PwnedPassword
3
- VERSION = '0.1.1'
5
+ VERSION = "0.1.2"
4
6
  end
5
7
  end
@@ -1,9 +1,14 @@
1
- require 'devise'
2
- require 'devise/pwned_password/model'
1
+ # frozen_string_literal: true
2
+
3
+ require "devise"
4
+ require "devise/pwned_password/model"
3
5
 
4
6
  module Devise
7
+ mattr_accessor :min_password_matches
8
+ @@min_password_matches = 1
9
+
5
10
  module PwnedPassword
6
11
  end
7
12
  end
8
13
 
9
- Devise.add_module :pwned_password, model: "devise_pwned_password/model"
14
+ Devise.add_module :pwned_password, model: "devise_pwned_password/model"
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  # desc "Explaining what the task does"
2
3
  # task :devise_pwned_password do
3
4
  # # Task goes here
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: devise-pwned_password
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michael Banfield
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-10-05 00:00:00.000000000 Z
11
+ date: 2018-02-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -52,6 +52,20 @@ dependencies:
52
52
  - - ">="
53
53
  - !ruby/object:Gem::Version
54
54
  version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rubocop
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: 0.52.1
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: 0.52.1
55
69
  description: Devise extension that checks user passwords against the PwnedPasswords
56
70
  dataset https://haveibeenpwned.com/Passwords.
57
71
  email:
@@ -87,7 +101,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
87
101
  version: '0'
88
102
  requirements: []
89
103
  rubyforge_project:
90
- rubygems_version: 2.6.12
104
+ rubygems_version: 2.5.2
91
105
  signing_key:
92
106
  specification_version: 4
93
107
  summary: Devise extension that checks user passwords against the PwnedPasswords dataset.