clearance 2.0.0.beta2 → 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.

Potentially problematic release.


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

Files changed (58) hide show
  1. checksums.yaml +4 -4
  2. data/.erb-lint.yml +5 -0
  3. data/.travis.yml +5 -9
  4. data/Appraisals +14 -19
  5. data/Gemfile +11 -7
  6. data/Gemfile.lock +137 -84
  7. data/NEWS.md +90 -11
  8. data/README.md +11 -24
  9. data/RELEASING.md +25 -0
  10. data/Rakefile +6 -1
  11. data/app/controllers/clearance/base_controller.rb +8 -1
  12. data/app/controllers/clearance/passwords_controller.rb +24 -7
  13. data/app/views/clearance_mailer/change_password.html.erb +2 -2
  14. data/app/views/clearance_mailer/change_password.text.erb +2 -2
  15. data/clearance.gemspec +10 -3
  16. data/config/locales/clearance.en.yml +1 -0
  17. data/config/routes.rb +1 -1
  18. data/gemfiles/rails_5.0.gemfile +10 -9
  19. data/gemfiles/rails_5.1.gemfile +11 -10
  20. data/gemfiles/rails_5.2.gemfile +11 -10
  21. data/gemfiles/rails_6.0.gemfile +11 -10
  22. data/lib/clearance/authentication.rb +1 -1
  23. data/lib/clearance/back_door.rb +2 -1
  24. data/lib/clearance/configuration.rb +29 -18
  25. data/lib/clearance/password_strategies.rb +2 -5
  26. data/lib/clearance/password_strategies/argon2.rb +23 -0
  27. data/lib/clearance/password_strategies/bcrypt.rb +17 -11
  28. data/lib/clearance/rack_session.rb +5 -1
  29. data/lib/clearance/session.rb +19 -2
  30. data/lib/clearance/testing/deny_access_matcher.rb +1 -5
  31. data/lib/clearance/user.rb +12 -3
  32. data/lib/clearance/version.rb +1 -1
  33. data/lib/generators/clearance/install/install_generator.rb +10 -0
  34. data/lib/generators/clearance/install/templates/README +10 -4
  35. data/lib/generators/clearance/install/templates/db/migrate/add_clearance_to_users.rb.erb +1 -1
  36. data/lib/generators/clearance/install/templates/db/migrate/create_users.rb.erb +1 -1
  37. data/lib/generators/clearance/routes/templates/routes.rb +1 -1
  38. data/spec/acceptance/clearance_installation_spec.rb +0 -4
  39. data/spec/app_templates/app/models/user.rb +1 -1
  40. data/spec/app_templates/testapp/app/views/layouts/application.html.erb +24 -0
  41. data/spec/clearance/back_door_spec.rb +20 -4
  42. data/spec/clearance/rack_session_spec.rb +2 -0
  43. data/spec/clearance/session_spec.rb +88 -8
  44. data/spec/clearance/testing/deny_access_matcher_spec.rb +32 -0
  45. data/spec/configuration_spec.rb +32 -14
  46. data/spec/controllers/passwords_controller_spec.rb +36 -0
  47. data/spec/dummy/app/controllers/application_controller.rb +1 -1
  48. data/spec/generators/clearance/install/install_generator_spec.rb +30 -1
  49. data/spec/generators/clearance/views/views_generator_spec.rb +0 -1
  50. data/spec/models/user_spec.rb +34 -5
  51. data/spec/password_strategies/argon2_spec.rb +79 -0
  52. data/spec/password_strategies/bcrypt_spec.rb +18 -1
  53. data/spec/requests/authentication_cookie_spec.rb +55 -0
  54. data/spec/spec_helper.rb +0 -1
  55. data/spec/support/generator_spec_helpers.rb +1 -5
  56. metadata +45 -15
  57. data/app/views/layouts/application.html.erb +0 -23
  58. data/spec/app_templates/app/models/rails5/user.rb +0 -5
data/README.md CHANGED
@@ -19,7 +19,7 @@ monitored by contributors.
19
19
 
20
20
  ## Getting Started
21
21
 
22
- Clearance is a Rails engine tested against Rails `>= 3.2` and Ruby `>= 1.9.3`.
22
+ Clearance is a Rails engine tested against Rails `>= 5.0` and Ruby `>= 2.4.0`.
23
23
 
24
24
  You can add it to your Gemfile with:
25
25
 
@@ -59,17 +59,15 @@ Clearance.configure do |config|
59
59
  config.mailer_sender = "reply@example.com"
60
60
  config.password_strategy = Clearance::PasswordStrategies::BCrypt
61
61
  config.redirect_url = "/"
62
- config.rotate_csrf_on_sign_in = false
62
+ config.rotate_csrf_on_sign_in = true
63
+ config.same_site = nil
63
64
  config.secure_cookie = false
64
65
  config.sign_in_guards = []
65
66
  config.user_model = "User"
67
+ config.parent_controller = "ApplicationController"
66
68
  end
67
69
  ```
68
70
 
69
- The install generator will set `rotate_csrf_on_sign_in` to `true`, so new
70
- installations will get this behavior from the start. This helps avoid session
71
- fixation attacks, and will become the default in Clearance 2.0.
72
-
73
71
  ## Use
74
72
 
75
73
  ### Access Control
@@ -281,23 +279,12 @@ for access to additional, user-contributed translations.
281
279
  See [lib/clearance/user.rb](/lib/clearance/user.rb) for the default behavior.
282
280
  You can override those methods as needed.
283
281
 
284
- ### Deliver Email in Background Job
285
-
286
- Clearance has a password reset mailer. If you are using Rails 4.2 and Clearance
287
- 1.6 or greater, Clearance will use ActiveJob's `deliver_later` method to
288
- automatically take advantage of your configured queue.
289
-
290
- If you are using an earlier version of Rails, you can override the
291
- `Clearance::Passwords` controller and define the behavior you need in the
292
- `deliver_email` method.
293
-
294
- ```ruby
295
- class PasswordsController < Clearance::PasswordsController
296
- def deliver_email(user)
297
- ClearanceMailer.delay.change_password(user)
298
- end
299
- end
300
- ```
282
+ Note that there are some model-level validations (see above link for detail)
283
+ which the `Clearance::User` module will add to the configured model class and
284
+ which may conflict with or duplicate already present validations on the `email`
285
+ and `password` attributes. Over-riding the `email_optional?` or
286
+ `skip_password_validation?` methods to return `true` will disable those
287
+ validations from being added.
301
288
 
302
289
  ## Extending Sign In
303
290
 
@@ -329,7 +316,7 @@ Here's an example custom guard to handle email confirmation:
329
316
 
330
317
  ```ruby
331
318
  Clearance.configure do |config|
332
- config.sign_in_guards = [EmailConfirmationGuard]
319
+ config.sign_in_guards = ["EmailConfirmationGuard"]
333
320
  end
334
321
  ```
335
322
 
@@ -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)
@@ -1,2 +1,9 @@
1
- class Clearance::BaseController < ApplicationController
1
+ module Clearance
2
+ # Top-level base class that all Clearance controllers inherit from.
3
+ # Inherits from `ApplicationController` by default and can be overridden by
4
+ # setting a new value with {Configuration#parent_controller=}.
5
+ # @!parse
6
+ # class BaseController < ApplicationController; end
7
+ class BaseController < Clearance.configuration.parent_controller
8
+ end
2
9
  end
@@ -2,6 +2,7 @@ require 'active_support/deprecation'
2
2
 
3
3
  class Clearance::PasswordsController < Clearance::BaseController
4
4
  before_action :ensure_existing_user, only: [:edit, :update]
5
+ before_action :ensure_email_present, only: [:create]
5
6
  skip_before_action :require_login, only: [:create, :edit, :new, :update], raise: false
6
7
 
7
8
  def new
@@ -31,7 +32,7 @@ class Clearance::PasswordsController < Clearance::BaseController
31
32
  def update
32
33
  @user = find_user_for_update
33
34
 
34
- if @user.update_password password_reset_params
35
+ if @user.update_password(password_from_password_reset_params)
35
36
  sign_in @user
36
37
  redirect_to url_after_update
37
38
  session[:password_reset_token] = nil
@@ -44,12 +45,11 @@ class Clearance::PasswordsController < Clearance::BaseController
44
45
  private
45
46
 
46
47
  def deliver_email(user)
47
- mail = ::ClearanceMailer.change_password(user)
48
- mail.deliver_later
48
+ ::ClearanceMailer.change_password(user).deliver_now
49
49
  end
50
50
 
51
- def password_reset_params
52
- params[:password_reset][:password]
51
+ def password_from_password_reset_params
52
+ params.dig(:password_reset, :password)
53
53
  end
54
54
 
55
55
  def find_user_by_id_and_confirmation_token
@@ -57,12 +57,16 @@ class Clearance::PasswordsController < Clearance::BaseController
57
57
  token = params[:token] || session[:password_reset_token]
58
58
 
59
59
  Clearance.configuration.user_model.
60
- find_by_id_and_confirmation_token params[user_param], token.to_s
60
+ find_by(id: params[user_param], confirmation_token: token.to_s)
61
+ end
62
+
63
+ def email_from_password_params
64
+ params.dig(:password, :email)
61
65
  end
62
66
 
63
67
  def find_user_for_create
64
68
  Clearance.configuration.user_model.
65
- find_by_normalized_email params[:password][:email]
69
+ find_by_normalized_email(email_from_password_params)
66
70
  end
67
71
 
68
72
  def find_user_for_edit
@@ -73,6 +77,13 @@ class Clearance::PasswordsController < Clearance::BaseController
73
77
  find_user_by_id_and_confirmation_token
74
78
  end
75
79
 
80
+ def ensure_email_present
81
+ if email_from_password_params.blank?
82
+ flash_failure_when_missing_email
83
+ render template: "passwords/new"
84
+ end
85
+ end
86
+
76
87
  def ensure_existing_user
77
88
  unless find_user_by_id_and_confirmation_token
78
89
  flash_failure_when_forbidden
@@ -92,6 +103,12 @@ class Clearance::PasswordsController < Clearance::BaseController
92
103
  default: t("flashes.failure_after_update"))
93
104
  end
94
105
 
106
+ def flash_failure_when_missing_email
107
+ flash.now[:alert] = translate(:missing_email,
108
+ scope: [:clearance, :controllers, :passwords],
109
+ default: t("flashes.failure_when_missing_email"))
110
+ end
111
+
95
112
  def url_after_update
96
113
  Clearance.configuration.redirect_url
97
114
  end
@@ -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
+ edit_user_password_url(@user, 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
+ <%= edit_user_password_url(@user, token: @user.confirmation_token) %>
4
4
 
5
- <%= raw t(".closing") %>
5
+ <%= t(".closing") %>
@@ -2,8 +2,9 @@ $LOAD_PATH.push File.expand_path('../lib', __FILE__)
2
2
  require 'clearance/version'
3
3
 
4
4
  Gem::Specification.new do |s|
5
- s.add_dependency 'bcrypt'
6
- s.add_dependency 'email_validator', '~> 1.4'
5
+ s.add_dependency 'bcrypt', '>= 3.1.1'
6
+ s.add_dependency 'argon2', '~> 2.0', '>= 2.0.2'
7
+ s.add_dependency 'email_validator', '~> 2.0'
7
8
  s.add_dependency 'railties', '>= 5.0'
8
9
  s.add_dependency 'activemodel', '>= 5.0'
9
10
  s.add_dependency 'activerecord', '>= 5.0'
@@ -28,7 +29,13 @@ Gem::Specification.new do |s|
28
29
  'Galen Frechette',
29
30
  'Josh Steiner'
30
31
  ]
31
- s.description = 'Rails authentication & authorization with email & password.'
32
+ s.description = <<-DESCRIPTION
33
+ Clearance is built to support authentication and authorization via an
34
+ email/password sign-in mechanism in applications.
35
+
36
+ It provides some core classes commonly used for these features, along with
37
+ some opinionated defaults - but is intended to be easy to override.
38
+ DESCRIPTION
32
39
  s.email = 'support@thoughtbot.com'
33
40
  s.extra_rdoc_files = %w(LICENSE README.md)
34
41
  s.files = `git ls-files`.split("\n")
@@ -17,6 +17,7 @@ en:
17
17
  failure_when_forbidden: Please double check the URL or try submitting
18
18
  the form again.
19
19
  failure_when_not_signed_in: Please sign in to continue.
20
+ failure_when_missing_email: Email can't be blank.
20
21
  helpers:
21
22
  label:
22
23
  password:
@@ -13,7 +13,7 @@ if Clearance.configuration.routes_enabled?
13
13
  only: Clearance.configuration.user_actions do
14
14
  resource :password,
15
15
  controller: 'clearance/passwords',
16
- only: [:create, :edit, :update]
16
+ only: [:edit, :update]
17
17
  end
18
18
 
19
19
  get '/sign_in' => 'clearance/sessions#new', as: 'sign_in'
@@ -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.beta2"
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: "../"
@@ -59,7 +59,7 @@ module Clearance
59
59
  # {SessionsController#create}.
60
60
  #
61
61
  # Signing in will also regenerate the CSRF token for the current session,
62
- # provided {Configuration#rotate_csrf_token_on_sign_in} is set.
62
+ # provided {Configuration#rotate_csrf_on_sign_in?} is set.
63
63
  def sign_in(user, &block)
64
64
  clearance_session.sign_in(user, &block)
65
65
 
@@ -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)
@@ -42,6 +42,16 @@ module Clearance
42
42
  # @return [Boolean]
43
43
  attr_accessor :httponly
44
44
 
45
+ # Same-site cookies ("First-Party-Only" or "First-Party") allow servers to
46
+ # mitigate the risk of CSRF and information leakage attacks by asserting
47
+ # that a particular cookie should only be sent with requests initiated from
48
+ # the same registrable domain.
49
+ # Defaults to `nil`. For more, see
50
+ # [RFC6265](https://tools.ietf.org/html/draft-west-first-party-cookies-06#section-4.1.1).
51
+ # and https://github.com/rack/rack/blob/6eda04886e3a57918ca2d6a482fda02a678fef0a/lib/rack/utils.rb#L232-L244
52
+ # @return [String]
53
+ attr_accessor :same_site
54
+
45
55
  # Controls the address the password reset email is sent from.
46
56
  # Defaults to reply@example.com.
47
57
  # @return [String]
@@ -88,7 +98,12 @@ module Clearance
88
98
  # The ActiveRecord class that represents users in your application.
89
99
  # Defaults to `::User`.
90
100
  # @return [ActiveRecord::Base]
91
- attr_accessor :user_model
101
+ attr_writer :user_model
102
+
103
+ # The controller class that all Clearance controllers will inherit from.
104
+ # Defaults to `::ApplicationController`.
105
+ # @return [ActionController::Base]
106
+ attr_writer :parent_controller
92
107
 
93
108
  # The array of allowed environments where `Clearance::BackDoor` is enabled.
94
109
  # Defaults to ["test", "ci", "development"]
@@ -103,18 +118,29 @@ module Clearance
103
118
  @cookie_name = "remember_token"
104
119
  @cookie_path = '/'
105
120
  @httponly = true
121
+ @same_site = nil
106
122
  @mailer_sender = 'reply@example.com'
107
123
  @redirect_url = '/'
108
- @rotate_csrf_on_sign_in = nil
124
+ @rotate_csrf_on_sign_in = true
109
125
  @routes = true
110
126
  @secure_cookie = false
111
127
  @sign_in_guards = []
112
128
  end
113
129
 
130
+ # The class representing the configured user model.
131
+ # In the default configuration, this is the `User` class.
132
+ # @return [Class]
114
133
  def user_model
115
134
  (@user_model || "User").to_s.constantize
116
135
  end
117
136
 
137
+ # The class representing the configured base controller.
138
+ # In the default configuration, this is the `ApplicationController` class.
139
+ # @return [Class]
140
+ def parent_controller
141
+ (@parent_controller || "ApplicationController").to_s.constantize
142
+ end
143
+
118
144
  # Is the user sign up route enabled?
119
145
  # @return [Boolean]
120
146
  def allow_sign_up?
@@ -167,22 +193,7 @@ module Clearance
167
193
  end
168
194
 
169
195
  def rotate_csrf_on_sign_in?
170
- if rotate_csrf_on_sign_in.nil?
171
- warn <<-EOM.squish
172
- Clearance's `rotate_csrf_on_sign_in` configuration setting is unset and
173
- will be treated as `false`. Setting this value to `true` is
174
- recommended to avoid session fixation attacks and will be the default
175
- in Clearance 2.0. It is recommended that you opt-in to this setting
176
- now and test your application. To silence this warning, set
177
- `rotate_csrf_on_sign_in` to `true` or `false` in Clearance's
178
- initializer.
179
-
180
- For more information on session fixation, see:
181
- https://www.owasp.org/index.php/Session_fixation
182
- EOM
183
- end
184
-
185
- rotate_csrf_on_sign_in
196
+ !!rotate_csrf_on_sign_in
186
197
  end
187
198
  end
188
199