clearance 2.0.0 → 2.3.1

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 (60) 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 +142 -87
  7. data/NEWS.md +94 -0
  8. data/README.md +4 -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 +16 -3
  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/app/views/passwords/edit.html.erb +1 -1
  16. data/clearance.gemspec +9 -2
  17. data/config/locales/clearance.en.yml +1 -0
  18. data/config/routes.rb +1 -1
  19. data/gemfiles/rails_5.0.gemfile +10 -9
  20. data/gemfiles/rails_5.1.gemfile +11 -10
  21. data/gemfiles/rails_5.2.gemfile +11 -10
  22. data/gemfiles/rails_6.0.gemfile +11 -10
  23. data/gemfiles/rails_6.1.gemfile +21 -0
  24. data/lib/clearance/authentication.rb +1 -1
  25. data/lib/clearance/back_door.rb +2 -1
  26. data/lib/clearance/configuration.rb +37 -18
  27. data/lib/clearance/password_strategies.rb +2 -5
  28. data/lib/clearance/password_strategies/argon2.rb +23 -0
  29. data/lib/clearance/rack_session.rb +5 -1
  30. data/lib/clearance/session.rb +40 -12
  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 +13 -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 +3 -2
  43. data/spec/clearance/session_spec.rb +154 -51
  44. data/spec/configuration_spec.rb +60 -14
  45. data/spec/controllers/passwords_controller_spec.rb +19 -5
  46. data/spec/dummy/app/controllers/application_controller.rb +1 -1
  47. data/spec/generators/clearance/install/install_generator_spec.rb +36 -1
  48. data/spec/generators/clearance/views/views_generator_spec.rb +0 -1
  49. data/spec/mailers/clearance_mailer_spec.rb +33 -0
  50. data/spec/models/user_spec.rb +34 -5
  51. data/spec/password_strategies/argon2_spec.rb +79 -0
  52. data/spec/requests/authentication_cookie_spec.rb +55 -0
  53. data/spec/spec_helper.rb +0 -1
  54. data/spec/support/clearance.rb +11 -0
  55. data/spec/support/generator_spec_helpers.rb +1 -5
  56. data/spec/support/request_with_remember_token.rb +8 -6
  57. metadata +42 -12
  58. data/.travis.yml +0 -32
  59. data/app/views/layouts/application.html.erb +0 -23
  60. data/spec/app_templates/app/models/rails5/user.rb +0 -5
data/README.md CHANGED
@@ -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
@@ -288,24 +286,6 @@ and `password` attributes. Over-riding the `email_optional?` or
288
286
  `skip_password_validation?` methods to return `true` will disable those
289
287
  validations from being added.
290
288
 
291
- ### Deliver Email in Background Job
292
-
293
- Clearance has a password reset mailer. If you are using Rails 4.2 and Clearance
294
- 1.6 or greater, Clearance will use ActiveJob's `deliver_later` method to
295
- automatically take advantage of your configured queue.
296
-
297
- If you are using an earlier version of Rails, you can override the
298
- `Clearance::Passwords` controller and define the behavior you need in the
299
- `deliver_email` method.
300
-
301
- ```ruby
302
- class PasswordsController < Clearance::PasswordsController
303
- def deliver_email(user)
304
- ClearanceMailer.delay.change_password(user)
305
- end
306
- end
307
- ```
308
-
309
289
  ## Extending Sign In
310
290
 
311
291
  By default, Clearance will sign in any user with valid credentials. If you need
@@ -336,7 +316,7 @@ Here's an example custom guard to handle email confirmation:
336
316
 
337
317
  ```ruby
338
318
  Clearance.configure do |config|
339
- config.sign_in_guards = [EmailConfirmationGuard]
319
+ config.sign_in_guards = ["EmailConfirmationGuard"]
340
320
  end
341
321
  ```
342
322
 
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)
@@ -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
@@ -44,8 +45,7 @@ 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
51
  def password_from_password_reset_params
@@ -57,7 +57,7 @@ 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
61
  end
62
62
 
63
63
  def email_from_password_params
@@ -77,6 +77,13 @@ class Clearance::PasswordsController < Clearance::BaseController
77
77
  find_user_by_id_and_confirmation_token
78
78
  end
79
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
+
80
87
  def ensure_existing_user
81
88
  unless find_user_by_id_and_confirmation_token
82
89
  flash_failure_when_forbidden
@@ -96,6 +103,12 @@ class Clearance::PasswordsController < Clearance::BaseController
96
103
  default: t("flashes.failure_after_update"))
97
104
  end
98
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
+
99
112
  def url_after_update
100
113
  Clearance.configuration.redirect_url
101
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
+ 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,7 +3,8 @@ 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 'email_validator', '~> 1.4'
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:
data/config/routes.rb CHANGED
@@ -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: "../"
@@ -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: "../"
@@ -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)
@@ -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
@@ -98,7 +106,12 @@ module Clearance
98
106
  # The ActiveRecord class that represents users in your application.
99
107
  # Defaults to `::User`.
100
108
  # @return [ActiveRecord::Base]
101
- attr_accessor :user_model
109
+ attr_writer :user_model
110
+
111
+ # The controller class that all Clearance controllers will inherit from.
112
+ # Defaults to `::ApplicationController`.
113
+ # @return [ActionController::Base]
114
+ attr_writer :parent_controller
102
115
 
103
116
  # The array of allowed environments where `Clearance::BackDoor` is enabled.
104
117
  # Defaults to ["test", "ci", "development"]
@@ -116,16 +129,37 @@ module Clearance
116
129
  @same_site = nil
117
130
  @mailer_sender = 'reply@example.com'
118
131
  @redirect_url = '/'
119
- @rotate_csrf_on_sign_in = nil
132
+ @rotate_csrf_on_sign_in = true
120
133
  @routes = true
121
134
  @secure_cookie = false
135
+ @signed_cookie = false
122
136
  @sign_in_guards = []
123
137
  end
124
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
+
149
+ # The class representing the configured user model.
150
+ # In the default configuration, this is the `User` class.
151
+ # @return [Class]
125
152
  def user_model
126
153
  (@user_model || "User").to_s.constantize
127
154
  end
128
155
 
156
+ # The class representing the configured base controller.
157
+ # In the default configuration, this is the `ApplicationController` class.
158
+ # @return [Class]
159
+ def parent_controller
160
+ (@parent_controller || "ApplicationController").to_s.constantize
161
+ end
162
+
129
163
  # Is the user sign up route enabled?
130
164
  # @return [Boolean]
131
165
  def allow_sign_up?
@@ -178,22 +212,7 @@ module Clearance
178
212
  end
179
213
 
180
214
  def rotate_csrf_on_sign_in?
181
- if rotate_csrf_on_sign_in.nil?
182
- warn <<-EOM.squish
183
- Clearance's `rotate_csrf_on_sign_in` configuration setting is unset and
184
- will be treated as `false`. Setting this value to `true` is
185
- recommended to avoid session fixation attacks and will be the default
186
- in Clearance 2.0. It is recommended that you opt-in to this setting
187
- now and test your application. To silence this warning, set
188
- `rotate_csrf_on_sign_in` to `true` or `false` in Clearance's
189
- initializer.
190
-
191
- For more information on session fixation, see:
192
- https://www.owasp.org/index.php/Session_fixation
193
- EOM
194
- end
195
-
196
- rotate_csrf_on_sign_in
215
+ !!rotate_csrf_on_sign_in
197
216
  end
198
217
  end
199
218