clearance 2.2.1 → 2.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.erb-lint.yml +5 -0
- data/.github/workflows/tests.yml +52 -0
- data/Appraisals +14 -19
- data/Gemfile +11 -7
- data/Gemfile.lock +112 -65
- data/NEWS.md +48 -0
- data/README.md +25 -14
- data/RELEASING.md +25 -0
- data/Rakefile +6 -1
- data/app/controllers/clearance/passwords_controller.rb +1 -2
- data/app/views/clearance_mailer/change_password.html.erb +2 -2
- data/app/views/clearance_mailer/change_password.text.erb +2 -2
- data/app/views/passwords/edit.html.erb +1 -1
- data/gemfiles/rails_5.0.gemfile +10 -9
- data/gemfiles/rails_5.1.gemfile +11 -10
- data/gemfiles/rails_5.2.gemfile +11 -10
- data/gemfiles/rails_6.0.gemfile +11 -10
- data/gemfiles/rails_6.1.gemfile +21 -0
- data/lib/clearance/authorization.rb +7 -1
- data/lib/clearance/back_door.rb +2 -1
- data/lib/clearance/configuration.rb +19 -0
- data/lib/clearance/password_strategies.rb +0 -4
- data/lib/clearance/rack_session.rb +1 -1
- data/lib/clearance/session.rb +24 -12
- data/lib/clearance/user.rb +1 -1
- data/lib/clearance/version.rb +1 -1
- data/lib/generators/clearance/install/install_generator.rb +4 -1
- data/lib/generators/clearance/install/templates/db/migrate/add_clearance_to_users.rb.erb +5 -1
- data/spec/clearance/back_door_spec.rb +20 -4
- data/spec/clearance/rack_session_spec.rb +1 -2
- data/spec/clearance/session_spec.rb +116 -43
- data/spec/configuration_spec.rb +28 -0
- data/spec/controllers/sessions_controller_spec.rb +13 -0
- data/spec/generators/clearance/install/install_generator_spec.rb +8 -2
- data/spec/mailers/clearance_mailer_spec.rb +33 -0
- data/spec/models/user_spec.rb +2 -2
- data/spec/support/clearance.rb +11 -0
- data/spec/support/request_with_remember_token.rb +8 -6
- metadata +7 -4
- data/.travis.yml +0 -28
data/README.md
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
# Clearance
|
2
2
|
|
3
|
-
[![Build Status](https://
|
3
|
+
[![Build Status](https://github.com/thoughtbot/clearance/actions/workflows/tests.yml/badge.svg)]( https://github.com/thoughtbot/clearance/actions/workflows/tests.yml?query=branch%3Amain)
|
4
4
|
[![Code Climate](https://codeclimate.com/github/thoughtbot/clearance.svg)](https://codeclimate.com/github/thoughtbot/clearance)
|
5
|
-
[![Documentation Quality](https://inch-ci.org/github/thoughtbot/clearance.svg?branch=
|
5
|
+
[![Documentation Quality](https://inch-ci.org/github/thoughtbot/clearance.svg?branch=main)](https://inch-ci.org/github/thoughtbot/clearance)
|
6
6
|
[![Reviewed by Hound](https://img.shields.io/badge/Reviewed_by-Hound-8E64B0.svg)](https://houndci.com)
|
7
7
|
|
8
8
|
Rails authentication with email & password.
|
@@ -55,12 +55,14 @@ Clearance.configure do |config|
|
|
55
55
|
config.cookie_name = "remember_token"
|
56
56
|
config.cookie_path = "/"
|
57
57
|
config.routes = true
|
58
|
-
config.httponly =
|
58
|
+
config.httponly = true
|
59
59
|
config.mailer_sender = "reply@example.com"
|
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
|
-
###
|
290
|
+
### Signed Cookies
|
289
291
|
|
290
|
-
|
291
|
-
|
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
|
-
|
295
|
-
|
296
|
-
|
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 signed 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
|
-
|
300
|
-
|
301
|
-
|
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
|
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
|
-
|
49
|
-
mail.deliver_later
|
48
|
+
::ClearanceMailer.change_password(user).deliver_now
|
50
49
|
end
|
51
50
|
|
52
51
|
def password_from_password_reset_params
|
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
<p>
|
4
4
|
<%= link_to t(".link_text", default: "Change my password"),
|
5
|
-
|
5
|
+
url_for([@user, :password, action: :edit, token: @user.confirmation_token]) %>
|
6
6
|
</p>
|
7
7
|
|
8
|
-
<p><%=
|
8
|
+
<p><%= t(".closing") %></p>
|
@@ -4,7 +4,7 @@
|
|
4
4
|
<p><%= t(".description") %></p>
|
5
5
|
|
6
6
|
<%= form_for :password_reset,
|
7
|
-
url:
|
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/gemfiles/rails_5.0.gemfile
CHANGED
@@ -2,19 +2,20 @@
|
|
2
2
|
|
3
3
|
source "https://rubygems.org"
|
4
4
|
|
5
|
-
gem "addressable"
|
5
|
+
gem "addressable"
|
6
6
|
gem "ammeter"
|
7
7
|
gem "appraisal"
|
8
|
-
gem "capybara", ">= 2.6.2"
|
9
|
-
gem "database_cleaner"
|
10
|
-
gem "
|
11
|
-
gem "
|
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: "../"
|
data/gemfiles/rails_5.1.gemfile
CHANGED
@@ -2,19 +2,20 @@
|
|
2
2
|
|
3
3
|
source "https://rubygems.org"
|
4
4
|
|
5
|
-
gem "addressable"
|
5
|
+
gem "addressable"
|
6
6
|
gem "ammeter"
|
7
7
|
gem "appraisal"
|
8
|
-
gem "capybara"
|
9
|
-
gem "database_cleaner"
|
10
|
-
gem "
|
11
|
-
gem "
|
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 "
|
18
|
-
gem "
|
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: "../"
|
data/gemfiles/rails_5.2.gemfile
CHANGED
@@ -2,19 +2,20 @@
|
|
2
2
|
|
3
3
|
source "https://rubygems.org"
|
4
4
|
|
5
|
-
gem "addressable"
|
5
|
+
gem "addressable"
|
6
6
|
gem "ammeter"
|
7
7
|
gem "appraisal"
|
8
|
-
gem "capybara"
|
9
|
-
gem "database_cleaner"
|
10
|
-
gem "
|
11
|
-
gem "
|
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 "
|
18
|
-
gem "
|
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: "../"
|
data/gemfiles/rails_6.0.gemfile
CHANGED
@@ -2,19 +2,20 @@
|
|
2
2
|
|
3
3
|
source "https://rubygems.org"
|
4
4
|
|
5
|
-
gem "addressable"
|
5
|
+
gem "addressable"
|
6
6
|
gem "ammeter"
|
7
7
|
gem "appraisal"
|
8
|
-
gem "capybara"
|
9
|
-
gem "database_cleaner"
|
10
|
-
gem "
|
11
|
-
gem "
|
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"
|
18
|
-
gem "
|
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: "../"
|
@@ -86,10 +86,16 @@ module Clearance
|
|
86
86
|
def return_to
|
87
87
|
if return_to_url
|
88
88
|
uri = URI.parse(return_to_url)
|
89
|
-
|
89
|
+
path = path_without_leading_slashes(uri)
|
90
|
+
"#{path}?#{uri.query}".chomp("?") + "##{uri.fragment}".chomp("#")
|
90
91
|
end
|
91
92
|
end
|
92
93
|
|
94
|
+
# @api private
|
95
|
+
def path_without_leading_slashes(uri)
|
96
|
+
uri.path.sub(/\A\/+/, "/")
|
97
|
+
end
|
98
|
+
|
93
99
|
# @api private
|
94
100
|
def return_to_url
|
95
101
|
session[:return_to]
|
data/lib/clearance/back_door.rb
CHANGED
@@ -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
|
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]
|
@@ -15,9 +15,5 @@ module Clearance
|
|
15
15
|
module PasswordStrategies
|
16
16
|
autoload :BCrypt, "clearance/password_strategies/bcrypt"
|
17
17
|
autoload :Argon2, "clearance/password_strategies/argon2"
|
18
|
-
autoload :BCryptMigrationFromSHA1,
|
19
|
-
"clearance/password_strategies/bcrypt_migration_from_sha1"
|
20
|
-
autoload :Blowfish, "clearance/password_strategies/blowfish"
|
21
|
-
autoload :SHA1, "clearance/password_strategies/sha1"
|
22
18
|
end
|
23
19
|
end
|
data/lib/clearance/session.rb
CHANGED
@@ -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
|
17
|
+
def add_cookie_to_headers
|
18
18
|
if signed_in_with_remember_token?
|
19
|
-
|
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
|
-
|
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:
|
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] =
|
190
|
+
options[:domain] = domain
|
179
191
|
end
|
180
192
|
end
|
181
193
|
end
|