clearance 2.1.0 → 2.4.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of clearance might be problematic. Click here for more details.

Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/.erb-lint.yml +5 -0
  3. data/.github/workflows/tests.yml +52 -0
  4. data/Appraisals +14 -19
  5. data/Gemfile +11 -7
  6. data/Gemfile.lock +140 -85
  7. data/NEWS.md +62 -0
  8. data/README.md +23 -12
  9. data/RELEASING.md +25 -0
  10. data/Rakefile +6 -1
  11. data/app/controllers/clearance/passwords_controller.rb +2 -3
  12. data/app/views/clearance_mailer/change_password.html.erb +2 -2
  13. data/app/views/clearance_mailer/change_password.text.erb +2 -2
  14. data/app/views/passwords/edit.html.erb +1 -1
  15. data/clearance.gemspec +1 -0
  16. data/gemfiles/rails_5.0.gemfile +10 -9
  17. data/gemfiles/rails_5.1.gemfile +11 -10
  18. data/gemfiles/rails_5.2.gemfile +11 -10
  19. data/gemfiles/rails_6.0.gemfile +11 -10
  20. data/gemfiles/rails_6.1.gemfile +21 -0
  21. data/lib/clearance/back_door.rb +2 -1
  22. data/lib/clearance/configuration.rb +19 -0
  23. data/lib/clearance/password_strategies.rb +2 -5
  24. data/lib/clearance/password_strategies/argon2.rb +23 -0
  25. data/lib/clearance/rack_session.rb +1 -1
  26. data/lib/clearance/session.rb +24 -12
  27. data/lib/clearance/user.rb +11 -2
  28. data/lib/clearance/version.rb +1 -1
  29. data/lib/generators/clearance/install/install_generator.rb +4 -1
  30. data/spec/clearance/back_door_spec.rb +20 -4
  31. data/spec/clearance/rack_session_spec.rb +1 -2
  32. data/spec/clearance/session_spec.rb +116 -43
  33. data/spec/configuration_spec.rb +28 -0
  34. data/spec/generators/clearance/install/install_generator_spec.rb +8 -2
  35. data/spec/mailers/clearance_mailer_spec.rb +33 -0
  36. data/spec/models/user_spec.rb +29 -0
  37. data/spec/password_strategies/argon2_spec.rb +79 -0
  38. data/spec/support/clearance.rb +11 -0
  39. data/spec/support/request_with_remember_token.rb +8 -6
  40. metadata +29 -4
  41. data/.travis.yml +0 -27
data/README.md CHANGED
@@ -60,7 +60,9 @@ Clearance.configure do |config|
60
60
  config.password_strategy = Clearance::PasswordStrategies::BCrypt
61
61
  config.redirect_url = "/"
62
62
  config.rotate_csrf_on_sign_in = true
63
+ config.same_site = nil
63
64
  config.secure_cookie = false
65
+ config.signed_cookie = false
64
66
  config.sign_in_guards = []
65
67
  config.user_model = "User"
66
68
  config.parent_controller = "ApplicationController"
@@ -285,24 +287,33 @@ and `password` attributes. Over-riding the `email_optional?` or
285
287
  `skip_password_validation?` methods to return `true` will disable those
286
288
  validations from being added.
287
289
 
288
- ### Deliver Email in Background Job
290
+ ### Signed Cookies
289
291
 
290
- Clearance has a password reset mailer. If you are using Rails 4.2 and Clearance
291
- 1.6 or greater, Clearance will use ActiveJob's `deliver_later` method to
292
- automatically take advantage of your configured queue.
292
+ By default, Clearance uses unsigned cookies. If you would like to use signed
293
+ cookies you can do so by overriding the default in an initializer like so:
293
294
 
294
- If you are using an earlier version of Rails, you can override the
295
- `Clearance::Passwords` controller and define the behavior you need in the
296
- `deliver_email` method.
295
+ ```ruby
296
+ Clearance.configure do |config|
297
+ # ... other overrides
298
+ config.signed_cookie = true
299
+ end
300
+ ```
301
+
302
+ If you are currently not using unsigned cookies but would like to migrate your
303
+ users over to them without breaking current sessions, you can do so by passing
304
+ in `:migrate` rather than `true` as so:
297
305
 
298
306
  ```ruby
299
- class PasswordsController < Clearance::PasswordsController
300
- def deliver_email(user)
301
- ClearanceMailer.delay.change_password(user)
302
- end
307
+ Clearance.configure do |config|
308
+ # ... other overrides
309
+ config.signed_cookie = :migrate
303
310
  end
304
311
  ```
305
312
 
313
+ You can read more about signed cookies in Clearance and why they are a good idea
314
+ in the [pull request that added them](https://github.com/thoughtbot/clearance/pull/917).
315
+
316
+
306
317
  ## Extending Sign In
307
318
 
308
319
  By default, Clearance will sign in any user with valid credentials. If you need
@@ -333,7 +344,7 @@ Here's an example custom guard to handle email confirmation:
333
344
 
334
345
  ```ruby
335
346
  Clearance.configure do |config|
336
- config.sign_in_guards = [EmailConfirmationGuard]
347
+ config.sign_in_guards = ["EmailConfirmationGuard"]
337
348
  end
338
349
  ```
339
350
 
data/RELEASING.md ADDED
@@ -0,0 +1,25 @@
1
+ # Releasing
2
+
3
+ 1. Update version file accordingly.
4
+ 1. Run `bundle install` to update Gemfile.lock
5
+ 1. Update `NEWS.md` to reflect the changes since last release.
6
+ 1. Commit changes.
7
+ There shouldn't be code changes,
8
+ and thus CI doesn't need to run,
9
+ you can then add "[ci skip]" to the commit message.
10
+ 1. Push the new commit
11
+ 1. Tag the release: `git tag -s vVERSION`
12
+ - We recommend the [_quick guide on how to sign a commit_] from GitHub.
13
+ 1. Push changes: `git push --tags`
14
+ 1. Build and publish:
15
+ ```bash
16
+ gem build clearance.gemspec
17
+ gem push clearance-*.gem
18
+ ```
19
+ 1. Add a new GitHub release using the recent `NEWS.md` as the content. Sample
20
+ URL: https://github.com/thoughtbot/clearance/releases/new?tag=vVERSION
21
+ 1. Announce the new release,
22
+ making sure to say "thank you" to the contributors
23
+ who helped shape this version!
24
+
25
+ [_quick guide on how to sign a commit_]: https://docs.github.com/en/github/authenticating-to-github/signing-commits
data/Rakefile CHANGED
@@ -22,5 +22,10 @@ RSpec::Core::RakeTask.new("spec:acceptance") do |task|
22
22
  task.verbose = false
23
23
  end
24
24
 
25
+ desc "Lint ERB templates"
26
+ task :erb_lint do
27
+ sh("bundle", "exec", "erblint", "app/views/**/*.erb")
28
+ end
29
+
25
30
  desc "Run the specs and acceptance tests"
26
- task default: %w(spec spec:acceptance)
31
+ task default: %w(spec spec:acceptance erb_lint)
@@ -45,8 +45,7 @@ class Clearance::PasswordsController < Clearance::BaseController
45
45
  private
46
46
 
47
47
  def deliver_email(user)
48
- mail = ::ClearanceMailer.change_password(user)
49
- mail.deliver_later
48
+ ::ClearanceMailer.change_password(user).deliver_now
50
49
  end
51
50
 
52
51
  def password_from_password_reset_params
@@ -58,7 +57,7 @@ class Clearance::PasswordsController < Clearance::BaseController
58
57
  token = params[:token] || session[:password_reset_token]
59
58
 
60
59
  Clearance.configuration.user_model.
61
- find_by_id_and_confirmation_token params[user_param], token.to_s
60
+ find_by(id: params[user_param], confirmation_token: token.to_s)
62
61
  end
63
62
 
64
63
  def email_from_password_params
@@ -2,7 +2,7 @@
2
2
 
3
3
  <p>
4
4
  <%= link_to t(".link_text", default: "Change my password"),
5
- edit_user_password_url(@user, token: @user.confirmation_token.html_safe) %>
5
+ url_for([@user, :password, action: :edit, token: @user.confirmation_token]) %>
6
6
  </p>
7
7
 
8
- <p><%= raw t(".closing") %></p>
8
+ <p><%= t(".closing") %></p>
@@ -1,5 +1,5 @@
1
1
  <%= t(".opening") %>
2
2
 
3
- <%= edit_user_password_url(@user, token: @user.confirmation_token.html_safe) %>
3
+ <%= url_for([@user, :password, action: :edit, token: @user.confirmation_token]) %>
4
4
 
5
- <%= raw t(".closing") %>
5
+ <%= t(".closing") %>
@@ -4,7 +4,7 @@
4
4
  <p><%= t(".description") %></p>
5
5
 
6
6
  <%= form_for :password_reset,
7
- url: user_password_path(@user, token: @user.confirmation_token),
7
+ url: [@user, :password, token: @user.confirmation_token],
8
8
  html: { method: :put } do |form| %>
9
9
  <div class="password-field">
10
10
  <%= form.label :password %>
data/clearance.gemspec CHANGED
@@ -3,6 +3,7 @@ require 'clearance/version'
3
3
 
4
4
  Gem::Specification.new do |s|
5
5
  s.add_dependency 'bcrypt', '>= 3.1.1'
6
+ s.add_dependency 'argon2', '~> 2.0', '>= 2.0.2'
6
7
  s.add_dependency 'email_validator', '~> 2.0'
7
8
  s.add_dependency 'railties', '>= 5.0'
8
9
  s.add_dependency 'activemodel', '>= 5.0'
@@ -2,19 +2,20 @@
2
2
 
3
3
  source "https://rubygems.org"
4
4
 
5
- gem "addressable", "~> 2.6.0"
5
+ gem "addressable"
6
6
  gem "ammeter"
7
7
  gem "appraisal"
8
- gem "capybara", ">= 2.6.2"
9
- gem "database_cleaner", "~> 1.0"
10
- gem "factory_bot_rails", "~> 5.0"
11
- gem "nokogiri", "~> 1.10.0"
8
+ gem "capybara", ">= 2.6.2", "< 3.33.0"
9
+ gem "database_cleaner"
10
+ gem "erb_lint", require: false
11
+ gem "factory_bot_rails"
12
+ gem "nokogiri"
12
13
  gem "pry", require: false
13
- gem "shoulda-matchers", "~> 4.1"
14
- gem "timecop", "~> 0.6"
15
- gem "railties", "~> 5.0.0"
16
14
  gem "rails-controller-testing"
17
- gem "sqlite3", "~> 1.3.13"
18
15
  gem "rspec-rails", "~> 3.1"
16
+ gem "shoulda-matchers"
17
+ gem "sqlite3", "~> 1.3.13"
18
+ gem "timecop"
19
+ gem "railties", "~> 5.0"
19
20
 
20
21
  gemspec path: "../"
@@ -2,19 +2,20 @@
2
2
 
3
3
  source "https://rubygems.org"
4
4
 
5
- gem "addressable", "~> 2.6.0"
5
+ gem "addressable"
6
6
  gem "ammeter"
7
7
  gem "appraisal"
8
- gem "capybara", ">= 2.6.2"
9
- gem "database_cleaner", "~> 1.0"
10
- gem "factory_bot_rails", "~> 5.0"
11
- gem "nokogiri", "~> 1.10.0"
8
+ gem "capybara"
9
+ gem "database_cleaner"
10
+ gem "erb_lint", require: false
11
+ gem "factory_bot_rails"
12
+ gem "nokogiri"
12
13
  gem "pry", require: false
13
- gem "shoulda-matchers", "~> 4.1"
14
- gem "timecop", "~> 0.6"
15
- gem "railties", "~> 5.1.0"
16
14
  gem "rails-controller-testing"
17
- gem "sqlite3", "~> 1.3.13"
18
- gem "rspec-rails", "~> 3.1"
15
+ gem "rspec-rails"
16
+ gem "shoulda-matchers"
17
+ gem "sqlite3"
18
+ gem "timecop"
19
+ gem "railties", "~> 5.1"
19
20
 
20
21
  gemspec path: "../"
@@ -2,19 +2,20 @@
2
2
 
3
3
  source "https://rubygems.org"
4
4
 
5
- gem "addressable", "~> 2.6.0"
5
+ gem "addressable"
6
6
  gem "ammeter"
7
7
  gem "appraisal"
8
- gem "capybara", ">= 2.6.2"
9
- gem "database_cleaner", "~> 1.0"
10
- gem "factory_bot_rails", "~> 5.0"
11
- gem "nokogiri", "~> 1.10.0"
8
+ gem "capybara"
9
+ gem "database_cleaner"
10
+ gem "erb_lint", require: false
11
+ gem "factory_bot_rails"
12
+ gem "nokogiri"
12
13
  gem "pry", require: false
13
- gem "shoulda-matchers", "~> 4.1"
14
- gem "timecop", "~> 0.6"
15
- gem "railties", "~> 5.2.0"
16
14
  gem "rails-controller-testing"
17
- gem "sqlite3", "~> 1.3.13"
18
- gem "rspec-rails", "~> 3.1"
15
+ gem "rspec-rails"
16
+ gem "shoulda-matchers"
17
+ gem "sqlite3"
18
+ gem "timecop"
19
+ gem "railties", "~> 5.2"
19
20
 
20
21
  gemspec path: "../"
@@ -2,19 +2,20 @@
2
2
 
3
3
  source "https://rubygems.org"
4
4
 
5
- gem "addressable", "~> 2.6.0"
5
+ gem "addressable"
6
6
  gem "ammeter"
7
7
  gem "appraisal"
8
- gem "capybara", ">= 2.6.2"
9
- gem "database_cleaner", "~> 1.0"
10
- gem "factory_bot_rails", "~> 5.0"
11
- gem "nokogiri", "~> 1.10.0"
8
+ gem "capybara"
9
+ gem "database_cleaner"
10
+ gem "erb_lint", require: false
11
+ gem "factory_bot_rails"
12
+ gem "nokogiri"
12
13
  gem "pry", require: false
13
- gem "shoulda-matchers", "~> 4.1"
14
- gem "timecop", "~> 0.6"
15
- gem "railties", "~> 6.0.0"
16
14
  gem "rails-controller-testing"
17
- gem "rspec-rails", "~> 4.0.0.beta3"
18
- gem "sqlite3", "~> 1.4.0"
15
+ gem "rspec-rails"
16
+ gem "shoulda-matchers"
17
+ gem "sqlite3"
18
+ gem "timecop"
19
+ gem "railties", "~> 6.0"
19
20
 
20
21
  gemspec path: "../"
@@ -0,0 +1,21 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "addressable"
6
+ gem "ammeter"
7
+ gem "appraisal"
8
+ gem "capybara"
9
+ gem "database_cleaner"
10
+ gem "erb_lint", require: false
11
+ gem "factory_bot_rails"
12
+ gem "nokogiri"
13
+ gem "pry", require: false
14
+ gem "rails-controller-testing"
15
+ gem "rspec-rails"
16
+ gem "shoulda-matchers"
17
+ gem "sqlite3"
18
+ gem "timecop"
19
+ gem "railties", "~> 6.1"
20
+
21
+ gemspec path: "../"
@@ -49,7 +49,8 @@ module Clearance
49
49
  # @api private
50
50
  def sign_in_through_the_back_door(env)
51
51
  params = Rack::Utils.parse_query(env["QUERY_STRING"])
52
- user_param = params["as"]
52
+ user_param = params.delete("as")
53
+ env["QUERY_STRING"] = Rack::Utils.build_query(params)
53
54
 
54
55
  if user_param.present?
55
56
  user = find_user(user_param)
@@ -88,6 +88,14 @@ module Clearance
88
88
  # @return [Boolean]
89
89
  attr_accessor :secure_cookie
90
90
 
91
+ # Controls whether cookies are signed.
92
+ # Defaults to `false` for backwards compatibility.
93
+ # When set, uses Rails' signed cookies
94
+ # (more secure against timing/brute-force attacks)
95
+ # see [ActionDispatch::Cookies](https://api.rubyonrails.org/classes/ActionDispatch/Cookies.html)
96
+ # @return [Boolean|:migrate]
97
+ attr_reader :signed_cookie
98
+
91
99
  # The array of sign in guards to run when signing a user in.
92
100
  # Defaults to an empty array. Sign in guards respond to `call` and are
93
101
  # initialized with a session and the current stack. Each guard can decide
@@ -124,9 +132,20 @@ module Clearance
124
132
  @rotate_csrf_on_sign_in = true
125
133
  @routes = true
126
134
  @secure_cookie = false
135
+ @signed_cookie = false
127
136
  @sign_in_guards = []
128
137
  end
129
138
 
139
+ def signed_cookie=(value)
140
+ if [true, false, :migrate].include? value
141
+ @signed_cookie = value
142
+ else
143
+ raise "Clearance's signed_cookie configuration value is invalid. " \
144
+ "Valid values are true, false, or :migrate. " \
145
+ "Set this option via Clearance.configure in an initializer"
146
+ end
147
+ end
148
+
130
149
  # The class representing the configured user model.
131
150
  # In the default configuration, this is the `User` class.
132
151
  # @return [Class]
@@ -13,10 +13,7 @@ module Clearance
13
13
  # `password=(new_password)`. For an example of how to implement these methods,
14
14
  # see {Clearance::PasswordStrategies::BCrypt}.
15
15
  module PasswordStrategies
16
- autoload :BCrypt, 'clearance/password_strategies/bcrypt'
17
- autoload :BCryptMigrationFromSHA1,
18
- 'clearance/password_strategies/bcrypt_migration_from_sha1'
19
- autoload :Blowfish, 'clearance/password_strategies/blowfish'
20
- autoload :SHA1, 'clearance/password_strategies/sha1'
16
+ autoload :BCrypt, "clearance/password_strategies/bcrypt"
17
+ autoload :Argon2, "clearance/password_strategies/argon2"
21
18
  end
22
19
  end
@@ -0,0 +1,23 @@
1
+ module Clearance
2
+ module PasswordStrategies
3
+ # Uses Argon2 to authenticate users and store encrypted passwords.
4
+
5
+ module Argon2
6
+ require "argon2"
7
+
8
+ def authenticated?(password)
9
+ if encrypted_password.present?
10
+ ::Argon2::Password.verify_password(password, encrypted_password)
11
+ end
12
+ end
13
+
14
+ def password=(new_password)
15
+ @password = new_password
16
+
17
+ if new_password.present?
18
+ self.encrypted_password = ::Argon2::Password.new.create(new_password)
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -23,7 +23,7 @@ module Clearance
23
23
  response = @app.call(env)
24
24
 
25
25
  if session.authentication_successful?
26
- session.add_cookie_to_headers response[1]
26
+ session.add_cookie_to_headers
27
27
  end
28
28
 
29
29
  response
@@ -14,15 +14,9 @@ module Clearance
14
14
  # Called by {RackSession} to add the Clearance session cookie to a response.
15
15
  #
16
16
  # @return [void]
17
- def add_cookie_to_headers(headers)
17
+ def add_cookie_to_headers
18
18
  if signed_in_with_remember_token?
19
- Rack::Utils.set_cookie_header!(
20
- headers,
21
- remember_token_cookie,
22
- cookie_options.merge(
23
- value: current_user.remember_token,
24
- ),
25
- )
19
+ set_remember_token(current_user.remember_token)
26
20
  end
27
21
  end
28
22
 
@@ -112,9 +106,27 @@ module Clearance
112
106
  @cookies ||= ActionDispatch::Request.new(@env).cookie_jar
113
107
  end
114
108
 
109
+ # @api private
110
+ def set_remember_token(token)
111
+ case Clearance.configuration.signed_cookie
112
+ when true, :migrate
113
+ cookies.signed[remember_token_cookie] = cookie_options(token)
114
+ when false
115
+ cookies[remember_token_cookie] = cookie_options(token)
116
+ end
117
+ remember_token
118
+ end
119
+
115
120
  # @api private
116
121
  def remember_token
117
- cookies[remember_token_cookie]
122
+ case Clearance.configuration.signed_cookie
123
+ when true
124
+ cookies.signed[remember_token_cookie]
125
+ when :migrate
126
+ cookies.signed[remember_token_cookie] || cookies[remember_token_cookie]
127
+ when false
128
+ cookies[remember_token_cookie]
129
+ end
118
130
  end
119
131
 
120
132
  # @api private
@@ -159,7 +171,7 @@ module Clearance
159
171
  end
160
172
 
161
173
  # @api private
162
- def cookie_options
174
+ def cookie_options(value)
163
175
  {
164
176
  domain: domain,
165
177
  expires: remember_token_expires,
@@ -167,7 +179,7 @@ module Clearance
167
179
  same_site: Clearance.configuration.same_site,
168
180
  path: Clearance.configuration.cookie_path,
169
181
  secure: Clearance.configuration.secure_cookie,
170
- value: remember_token,
182
+ value: value,
171
183
  }
172
184
  end
173
185
 
@@ -175,7 +187,7 @@ module Clearance
175
187
  def delete_cookie_options
176
188
  Hash.new.tap do |options|
177
189
  if configured_cookie_domain
178
- options[:domain] = configured_cookie_domain
190
+ options[:domain] = domain
179
191
  end
180
192
  end
181
193
  end