devise-unpwn 0.9

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: ad6db676f32e9c18ae0589c9d774ebfd8bb17c456f1f65bf7ec715d090127ceb
4
+ data.tar.gz: de2f820bc97b83efaed6d5a9e59e319c3cf9b85a2762735e49f71b9c98421620
5
+ SHA512:
6
+ metadata.gz: a70ad60f8892de118e70f45eb52fc8a568b21c835a1fdccbf1a5106fad80db998d02a8c4eabff879a2e86704116ac7e5e39a002af0b875ea1915d830443bb67e
7
+ data.tar.gz: 81d0c716c7e3238a5eb1d57ed8b4d580e8baa1da19d7eab76c7c1e8328ebd2390d3af6001d917ca5e1e61a95098a492b27549a7613bbfb52de786de5f1bfbb47
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2017 Michael
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,125 @@
1
+ # Devise::Unpwn
2
+ Devise extension that checks user passwords against the HaveIBeenPwned dataset https://haveibeenpwned.com/Passwords
3
+
4
+ Based on
5
+
6
+ https://github.com/michaelbanfield/devise-pwned_password
7
+
8
+
9
+ ## Usage
10
+ Add the :unpwned module to your existing Devise model.
11
+
12
+ ```ruby
13
+ class AdminUser < ApplicationRecord
14
+ devise :database_authenticatable,
15
+ :recoverable, :rememberable, :trackable, :validatable, :unpwned
16
+ end
17
+ ```
18
+
19
+ Users will receive the following error message if they use a password from the
20
+ HaveIBeenPwned dataset:
21
+
22
+ ```
23
+ Password has previously appeared in a data breach and should never be used. Please choose something harder to guess.
24
+ ```
25
+
26
+ You can customize this error message by modifying the `devise` YAML file.
27
+
28
+ ```yml
29
+ # config/locales/devise.en.yml
30
+ en:
31
+ errors:
32
+ messages:
33
+ pwned_password: "has previously appeared in a data breach and should never be used. If you've ever used it anywhere before, change it immediately!"
34
+ ```
35
+
36
+ You can optionally warn existing users when they sign in if they are using a password from the HaveIBeenPwned dataset. The default message is:
37
+
38
+ ```
39
+ Your password has previously appeared in a data breach and should never be used. We strongly recommend you change your password.
40
+ ```
41
+
42
+ You can customize this message by modifying the `devise` YAML file.
43
+
44
+ ```yml
45
+ # config/locales/devise.en.yml
46
+ en:
47
+ devise:
48
+ sessions:
49
+ warn_pwned: "Your password has previously appeared in a data breach and should never be used. We strongly recommend you change your password everywhere you have used it."
50
+ ```
51
+
52
+ By default passwords are rejected if they appear at all in the data set.
53
+ Optionally, you can add the following snippet to `config/initializers/devise.rb`
54
+ if you want the error message to be displayed only when the password is present
55
+ a certain number of times in the data set:
56
+
57
+ ```ruby
58
+ # Minimum number of times a pwned password must exist in the data set in order
59
+ # to be reject.
60
+ config.min_password_matches = 10
61
+ ```
62
+
63
+ By default responses from the HaveIBeenPwned API are timed out after 5 seconds
64
+ to reduce potential latency problems.
65
+ Optionally, you can add the following snippet to `config/initializers/devise.rb`
66
+ to control the timeout settings:
67
+
68
+ ```ruby
69
+ config.unpwn_open_timeout = 1
70
+ config.unpwn_read_timeout = 2
71
+ ```
72
+
73
+ ## Installation
74
+
75
+ ```bash
76
+ bundle add devise-unpwn
77
+ ```
78
+
79
+ Optionally, if you also want to warn existing users when they sign in, override `after_sign_in_path_for`
80
+ ```ruby
81
+ def after_sign_in_path_for(resource)
82
+ set_flash_message! :alert, :warn_pwned if resource.respond_to?(:pwned?) && resource.pwned?
83
+ super
84
+ end
85
+ ```
86
+
87
+ This should generally be added in `app/controllers/application_controller.rb` for a rails app. For an Active Admin application the following monkey patch is needed.
88
+
89
+ ```ruby
90
+ # config/initializers/active_admin_devise_sessions_controller.rb
91
+ class ActiveAdmin::Devise::SessionsController
92
+ def after_sign_in_path_for(resource)
93
+ set_flash_message! :alert, :warn_pwned if resource.respond_to?(:pwned?) && resource.pwned?
94
+ super
95
+ end
96
+ end
97
+ ```
98
+
99
+
100
+ ## Considerations
101
+
102
+ A few things to consider/understand when using this gem:
103
+
104
+ * User passwords are hashed using SHA-1 and then truncated to 5 characters,
105
+ implementing the k-Anonymity model described in
106
+ https://haveibeenpwned.com/API/v2#SearchingPwnedPasswordsByRange
107
+ Neither the clear-text password nor the full password hash is ever transmitted
108
+ to a third party. More implementation details and important caveats can be
109
+ found in https://blog.cloudflare.com/validating-leaked-passwords-with-k-anonymity/
110
+
111
+ * This puts an external API in the request path of users signing up to your
112
+ application. This could potentially add some latency to this operation. The
113
+ gem is designed to fail silently if the HaveIBeenPwned service is unavailable.
114
+
115
+ ## Contributing
116
+
117
+ To contribute
118
+
119
+ * Fork the repository
120
+ * Make your changes
121
+ * Run bin/test to make sure the unit tests still run
122
+ * Send a pull request
123
+
124
+ ## License
125
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ begin
4
+ require "bundler/setup"
5
+ require "bundler/gem_tasks"
6
+ rescue LoadError
7
+ puts "You must `gem install bundler` and `bundle install` to run rake tasks"
8
+ end
9
+
10
+ require "rdoc/task"
11
+
12
+ RDoc::Task.new(:rdoc) do |rdoc|
13
+ rdoc.rdoc_dir = "rdoc"
14
+ rdoc.title = "Devise::Unpwn"
15
+ rdoc.options << "--line-numbers"
16
+ rdoc.rdoc_files.include("README.md")
17
+ rdoc.rdoc_files.include("lib/**/*.rb")
18
+ end
19
+
20
+ require "bundler/gem_tasks"
21
+
22
+ require "rake/testtask"
23
+
24
+ Rake::TestTask.new(:test) do |t|
25
+ t.libs << "test"
26
+ t.pattern = "test/**/*_test.rb"
27
+ t.verbose = false
28
+ end
29
+
30
+ task default: :test
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ Warden::Manager.after_set_user except: :fetch do |user, auth, opts|
4
+ password = auth.request.params.fetch(opts[:scope], {}).fetch(:password, nil)
5
+ password && auth.authenticated?(opts[:scope]) && user.respond_to?(:password_pwned?) && user.password_pwned?(password)
6
+ end
@@ -0,0 +1,7 @@
1
+ en:
2
+ devise:
3
+ sessions:
4
+ warn_pwned: "Your password has previously appeared in a data breach and should never be used. We strongly recommend you change your password."
5
+ errors:
6
+ messages:
7
+ pwned_password: "has previously appeared in a data breach and should never be used. Please choose something harder to guess."
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "net/http"
4
+ require "devise/unpwn/hooks/unpwn"
5
+
6
+ module Devise
7
+ module Models
8
+ # The Unpwn module adds a new validation for Devise Models.
9
+ # No modifications to routes or controllers needed.
10
+ # Simply add :unpwned to the list of included modules in your
11
+ # devise module, and all new registrations will be blocked if they use
12
+ # a password in this dataset https://haveibeenpwned.com/Passwords.
13
+ module Unpwned
14
+ extend ActiveSupport::Concern
15
+
16
+ included do
17
+ validate :unpwned_password, if: :password_required?
18
+ end
19
+
20
+ module ClassMethods
21
+ Devise::Models.config(self, :min_password_matches)
22
+ Devise::Models.config(self, :unpwn_open_timeout)
23
+ Devise::Models.config(self, :unpwn_read_timeout)
24
+ end
25
+
26
+ def pwned?
27
+ @pwned ||= false
28
+ end
29
+
30
+ # Returns true if password is present in the HaveIBeenPwned dataset
31
+ # Implement retry behaviour described here https://haveibeenpwned.com/API/v2#RateLimiting
32
+ def password_pwned?(password)
33
+ @pwned = false
34
+ hash = Digest::SHA1.hexdigest(password.to_s).upcase
35
+ prefix, suffix = hash.slice!(0..4), hash
36
+
37
+ user_agent = "devise_unpwn"
38
+
39
+ uri = URI.parse("https://api.pwnedpasswords.com/range/#{prefix}")
40
+
41
+ begin
42
+ Net::HTTP.start(uri.host, uri.port, use_ssl: true, open_timeout: self.class.unpwn_open_timeout, read_timeout: self.class.unpwn_read_timeout) do |http|
43
+ request = Net::HTTP::Get.new(uri.request_uri, "User-Agent" => user_agent)
44
+ response = http.request request
45
+ return false unless response.is_a?(Net::HTTPSuccess)
46
+ @pwned = usage_count(response.read_body, suffix) >= self.class.min_password_matches
47
+ return @pwned
48
+ end
49
+ rescue
50
+ return false
51
+ end
52
+
53
+ false
54
+ end
55
+
56
+ private
57
+
58
+ def usage_count(response, suffix)
59
+ count = 0
60
+ response.each_line do |line|
61
+ if line.start_with? suffix
62
+ count = line.strip.split(":").last.to_i
63
+ break
64
+ end
65
+ end
66
+ count
67
+ end
68
+
69
+ def unpwned_password
70
+ # 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
71
+ if password_pwned?(password)
72
+ errors.add(:password, :pwned_password)
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Devise
4
+ module Unpwn
5
+ VERSION = "0.9"
6
+ end
7
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "devise"
4
+ require "devise/unpwn/model"
5
+
6
+ module Devise
7
+ mattr_accessor :min_password_matches, :unpwn_open_timeout, :unpwn_read_timeout
8
+ @@min_password_matches = 1
9
+ @@unpwn_open_timeout = 5
10
+ @@unpwn_read_timeout = 5
11
+
12
+ module Unpwn
13
+ end
14
+ end
15
+
16
+ # Load default I18n
17
+ #
18
+ I18n.load_path.unshift File.join(File.dirname(__FILE__), *%w[unpwn locales en.yml])
19
+
20
+ Devise.add_module :unpwn, model: "devise/unpwn/model"
metadata ADDED
@@ -0,0 +1,66 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: devise-unpwn
3
+ version: !ruby/object:Gem::Version
4
+ version: '0.9'
5
+ platform: ruby
6
+ authors:
7
+ - André Arko
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2022-11-13 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: devise
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '4'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '4'
27
+ description: Devise extension that checks user passwords against the HaveIBeenPwned
28
+ dataset.
29
+ email:
30
+ - andre@arko.net
31
+ executables: []
32
+ extensions: []
33
+ extra_rdoc_files: []
34
+ files:
35
+ - MIT-LICENSE
36
+ - README.md
37
+ - Rakefile
38
+ - lib/devise/unpwn.rb
39
+ - lib/devise/unpwn/hooks/unpwn.rb
40
+ - lib/devise/unpwn/locales/en.yml
41
+ - lib/devise/unpwn/model.rb
42
+ - lib/devise/unpwn/version.rb
43
+ homepage: https://github.com/indirect/devise-unpwn
44
+ licenses:
45
+ - MIT
46
+ metadata: {}
47
+ post_install_message:
48
+ rdoc_options: []
49
+ require_paths:
50
+ - lib
51
+ required_ruby_version: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ version: '0'
56
+ required_rubygems_version: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ requirements: []
62
+ rubygems_version: 3.3.7
63
+ signing_key:
64
+ specification_version: 4
65
+ summary: Devise extension that checks user passwords against the HaveIBeenPwned dataset.
66
+ test_files: []