clearance 2.3.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.

checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 777ea26a3647f9d843995457a9fc073036fe60c7e3119c97a063345fbe85a7df
4
- data.tar.gz: c57adcad1e84434b6ffc9dc6e4af275d14b1c4844736536ecf32ee00180f4429
3
+ metadata.gz: 73e524b6026ced3c81ba4f5755fcc40190b5ca08e058d4297780600dc09dfa9a
4
+ data.tar.gz: 5c8fe49a083f5bddf070ed33eed1c78b5154d5da2c4f6bb3b52f5709c3db7875
5
5
  SHA512:
6
- metadata.gz: fb8a5c80c163df705026f5ebbfde61ed51475abe3ce24681dce56bbdb4394b37cf491e4ec8aecf33c00d4cc11f1a8826afd45c49a3465864d825da5b651d8e64
7
- data.tar.gz: 3f24bdba4f83baf3a62ec80457a62e4d8dccec82f1f5b2f37d4b9514badffde150dc2b85b6e207a91a875264a85e679da49a9fb38e22c898bcfb1d200f537161
6
+ metadata.gz: b8f2689813bcd73ed5d8cd9f5783f3659dbf001f924af4c595c2a5470ad5d1b9d9f57126117626204f0cec9e13b989d757e4baa33e077bc7b6cfde394d6a2f3d
7
+ data.tar.gz: ac38abe61a29243c8e253954accad74c8ada5532876b53483ce4991b745124c265674a6df908814a78b3ef4d467e8abd27e9355332e9162bfd25865f8b7bea2b
@@ -0,0 +1,52 @@
1
+ name: CI Tests
2
+
3
+ on:
4
+ push:
5
+ branches: "master"
6
+ pull_request:
7
+ branches: "*"
8
+
9
+ jobs:
10
+ test:
11
+ name: "Ruby ${{ matrix.ruby }}, Rails ${{ matrix.gemfile }}"
12
+
13
+ runs-on: ubuntu-latest
14
+
15
+ strategy:
16
+ fail-fast: false
17
+ matrix:
18
+ gemfile:
19
+ - "5.0"
20
+ - "5.1"
21
+ - "5.2"
22
+ - "6.0"
23
+ - "6.1"
24
+ ruby:
25
+ - "2.4.9"
26
+ - "2.5.7"
27
+ - "2.6.5"
28
+ - "2.7.2"
29
+ exclude:
30
+ - gemfile: "6.0"
31
+ ruby: "2.4.9"
32
+ - gemfile: "6.1"
33
+ ruby: "2.4.9"
34
+
35
+ env:
36
+ BUNDLE_GEMFILE: gemfiles/rails_${{ matrix.gemfile }}.gemfile
37
+ RAILS_ENV: test
38
+
39
+ steps:
40
+ - uses: actions/checkout@v2
41
+
42
+ - name: "Install Ruby ${{ matrix.ruby }}"
43
+ uses: ruby/setup-ruby@v1
44
+ with:
45
+ ruby-version: ${{ matrix.ruby }}
46
+ bundler-cache: true
47
+
48
+ - name: "Reset app database"
49
+ run: bundle exec rake dummy:db:reset
50
+
51
+ - name: "Run tests"
52
+ run: bundle exec rake
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- clearance (2.3.0)
4
+ clearance (2.3.1)
5
5
  actionmailer (>= 5.0)
6
6
  activemodel (>= 5.0)
7
7
  activerecord (>= 5.0)
@@ -13,39 +13,40 @@ PATH
13
13
  GEM
14
14
  remote: https://rubygems.org/
15
15
  specs:
16
- actionmailer (6.0.3.2)
17
- actionpack (= 6.0.3.2)
18
- actionview (= 6.0.3.2)
19
- activejob (= 6.0.3.2)
16
+ actionmailer (6.1.3)
17
+ actionpack (= 6.1.3)
18
+ actionview (= 6.1.3)
19
+ activejob (= 6.1.3)
20
+ activesupport (= 6.1.3)
20
21
  mail (~> 2.5, >= 2.5.4)
21
22
  rails-dom-testing (~> 2.0)
22
- actionpack (6.0.3.2)
23
- actionview (= 6.0.3.2)
24
- activesupport (= 6.0.3.2)
25
- rack (~> 2.0, >= 2.0.8)
23
+ actionpack (6.1.3)
24
+ actionview (= 6.1.3)
25
+ activesupport (= 6.1.3)
26
+ rack (~> 2.0, >= 2.0.9)
26
27
  rack-test (>= 0.6.3)
27
28
  rails-dom-testing (~> 2.0)
28
29
  rails-html-sanitizer (~> 1.0, >= 1.2.0)
29
- actionview (6.0.3.2)
30
- activesupport (= 6.0.3.2)
30
+ actionview (6.1.3)
31
+ activesupport (= 6.1.3)
31
32
  builder (~> 3.1)
32
33
  erubi (~> 1.4)
33
34
  rails-dom-testing (~> 2.0)
34
35
  rails-html-sanitizer (~> 1.1, >= 1.2.0)
35
- activejob (6.0.3.2)
36
- activesupport (= 6.0.3.2)
36
+ activejob (6.1.3)
37
+ activesupport (= 6.1.3)
37
38
  globalid (>= 0.3.6)
38
- activemodel (6.0.3.2)
39
- activesupport (= 6.0.3.2)
40
- activerecord (6.0.3.2)
41
- activemodel (= 6.0.3.2)
42
- activesupport (= 6.0.3.2)
43
- activesupport (6.0.3.2)
39
+ activemodel (6.1.3)
40
+ activesupport (= 6.1.3)
41
+ activerecord (6.1.3)
42
+ activemodel (= 6.1.3)
43
+ activesupport (= 6.1.3)
44
+ activesupport (6.1.3)
44
45
  concurrent-ruby (~> 1.0, >= 1.0.2)
45
- i18n (>= 0.7, < 2)
46
- minitest (~> 5.1)
47
- tzinfo (~> 1.1)
48
- zeitwerk (~> 2.2, >= 2.2.2)
46
+ i18n (>= 1.6, < 2)
47
+ minitest (>= 5.1)
48
+ tzinfo (~> 2.0)
49
+ zeitwerk (~> 2.3)
49
50
  addressable (2.7.0)
50
51
  public_suffix (>= 2.0.2, < 5.0)
51
52
  ammeter (1.1.4)
@@ -56,12 +57,12 @@ GEM
56
57
  bundler
57
58
  rake
58
59
  thor (>= 0.14.0)
59
- argon2 (2.0.2)
60
- ffi (~> 1.9)
61
- ffi-compiler (>= 0.1)
62
- ast (2.4.1)
63
- bcrypt (3.1.15)
64
- better_html (1.0.15)
60
+ argon2 (2.0.3)
61
+ ffi (~> 1.14)
62
+ ffi-compiler (~> 1.0)
63
+ ast (2.4.2)
64
+ bcrypt (3.1.16)
65
+ better_html (1.0.16)
65
66
  actionview (>= 4.0)
66
67
  activesupport (>= 4.0)
67
68
  ast (~> 2.0)
@@ -79,11 +80,11 @@ GEM
79
80
  regexp_parser (~> 1.5)
80
81
  xpath (~> 3.2)
81
82
  coderay (1.1.3)
82
- concurrent-ruby (1.1.6)
83
+ concurrent-ruby (1.1.8)
83
84
  crass (1.0.6)
84
85
  database_cleaner (1.8.5)
85
86
  diff-lcs (1.4.4)
86
- email_validator (2.0.1)
87
+ email_validator (2.2.2)
87
88
  activemodel
88
89
  erb_lint (0.0.34)
89
90
  activesupport
@@ -92,39 +93,41 @@ GEM
92
93
  rainbow
93
94
  rubocop (~> 0.79)
94
95
  smart_properties
95
- erubi (1.9.0)
96
+ erubi (1.10.0)
96
97
  factory_bot (6.1.0)
97
98
  activesupport (>= 5.0.0)
98
99
  factory_bot_rails (6.1.0)
99
100
  factory_bot (~> 6.1.0)
100
101
  railties (>= 5.0.0)
101
- ffi (1.13.1)
102
+ ffi (1.14.2)
102
103
  ffi-compiler (1.0.1)
103
104
  ffi (>= 1.0.0)
104
105
  rake
105
106
  globalid (0.4.2)
106
107
  activesupport (>= 4.2.0)
107
108
  html_tokenizer (0.0.7)
108
- i18n (1.8.5)
109
+ i18n (1.8.9)
109
110
  concurrent-ruby (~> 1.0)
110
- loofah (2.6.0)
111
+ loofah (2.9.0)
111
112
  crass (~> 1.0.2)
112
113
  nokogiri (>= 1.5.9)
113
114
  mail (2.7.1)
114
115
  mini_mime (>= 0.1.1)
115
116
  method_source (1.0.0)
116
117
  mini_mime (1.0.2)
117
- mini_portile2 (2.4.0)
118
- minitest (5.14.1)
119
- nokogiri (1.10.10)
120
- mini_portile2 (~> 2.4.0)
118
+ mini_portile2 (2.5.0)
119
+ minitest (5.14.4)
120
+ nokogiri (1.11.1)
121
+ mini_portile2 (~> 2.5.0)
122
+ racc (~> 1.4)
121
123
  parallel (1.19.2)
122
- parser (2.7.1.4)
124
+ parser (3.0.0.0)
123
125
  ast (~> 2.4.1)
124
126
  pry (0.13.1)
125
127
  coderay (~> 1.1)
126
128
  method_source (~> 1.0)
127
129
  public_suffix (4.0.5)
130
+ racc (1.5.2)
128
131
  rack (2.2.3)
129
132
  rack-test (1.1.0)
130
133
  rack (>= 1.0, < 3)
@@ -137,14 +140,14 @@ GEM
137
140
  nokogiri (>= 1.6)
138
141
  rails-html-sanitizer (1.3.0)
139
142
  loofah (~> 2.3)
140
- railties (6.0.3.2)
141
- actionpack (= 6.0.3.2)
142
- activesupport (= 6.0.3.2)
143
+ railties (6.1.3)
144
+ actionpack (= 6.1.3)
145
+ activesupport (= 6.1.3)
143
146
  method_source
144
147
  rake (>= 0.8.7)
145
- thor (>= 0.20.3, < 2.0)
148
+ thor (~> 1.0)
146
149
  rainbow (3.0.0)
147
- rake (13.0.1)
150
+ rake (13.0.3)
148
151
  regexp_parser (1.7.1)
149
152
  rexml (3.2.4)
150
153
  rspec-core (3.9.2)
@@ -180,15 +183,14 @@ GEM
180
183
  activesupport (>= 4.2.0)
181
184
  smart_properties (1.15.0)
182
185
  sqlite3 (1.4.2)
183
- thor (1.0.1)
184
- thread_safe (0.3.6)
186
+ thor (1.1.0)
185
187
  timecop (0.9.1)
186
- tzinfo (1.2.7)
187
- thread_safe (~> 0.1)
188
+ tzinfo (2.0.4)
189
+ concurrent-ruby (~> 1.0)
188
190
  unicode-display_width (1.7.0)
189
191
  xpath (3.2.0)
190
192
  nokogiri (~> 1.8)
191
- zeitwerk (2.4.0)
193
+ zeitwerk (2.4.2)
192
194
 
193
195
  PLATFORMS
194
196
  ruby
data/NEWS.md CHANGED
@@ -3,6 +3,16 @@
3
3
  The noteworthy changes for each Clearance version are included here. For a
4
4
  complete changelog, see the git history for each version via the version links.
5
5
 
6
+ ## [2.3.1] - March 5, 2021
7
+
8
+ ### Fixed
9
+
10
+ - Support for accessing Rails 6.x primary_key_type in generator.
11
+ - Fix password reset URLs when using a custom model
12
+ - Fix flaky test that relied on too specific time delta
13
+ - Revert case sensitivity for email uniqueness
14
+ - Bump nokogiri and actionview dependencies to address security vulnerabilities
15
+
6
16
  ## [2.3.0] - August 14, 2020
7
17
 
8
18
  ### Fixed
@@ -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) %>
5
+ url_for([@user, :password, action: :edit, token: @user.confirmation_token]) %>
6
6
  </p>
7
7
 
8
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) %>
3
+ <%= url_for([@user, :password, action: :edit, token: @user.confirmation_token]) %>
4
4
 
5
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 %>
@@ -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: "../"
@@ -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]
@@ -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
 
@@ -152,7 +152,7 @@ module Clearance
152
152
  validates :email,
153
153
  email: { strict_mode: true },
154
154
  presence: true,
155
- uniqueness: { allow_blank: true, case_sensitive: false },
155
+ uniqueness: { allow_blank: true, case_sensitive: true },
156
156
  unless: :email_optional?
157
157
 
158
158
  validates :password, presence: true, unless: :skip_password_validation?
@@ -1,3 +1,3 @@
1
1
  module Clearance
2
- VERSION = "2.3.0".freeze
2
+ VERSION = "2.3.1".freeze
3
3
  end
@@ -129,7 +129,10 @@ module Clearance
129
129
  end
130
130
 
131
131
  def configured_key_type
132
- Rails.configuration.generators.active_record[:primary_key_type]
132
+ active_record = Rails.configuration.generators.active_record
133
+ active_record ||= Rails.configuration.generators.options[:active_record]
134
+
135
+ active_record[:primary_key_type]
133
136
  end
134
137
 
135
138
  def models_inherit_from
@@ -20,7 +20,6 @@ describe Clearance::RackSession do
20
20
  response = Rack::MockResponse.new(*app.call(env))
21
21
 
22
22
  expect(response.body).to eq expected_session
23
- expect(expected_session).to have_received(:add_cookie_to_headers).
24
- with(hash_including(headers))
23
+ expect(expected_session).to have_received(:add_cookie_to_headers)
25
24
  end
26
25
  end
@@ -4,7 +4,6 @@ describe Clearance::Session do
4
4
  before { Timecop.freeze }
5
5
  after { Timecop.return }
6
6
 
7
- let(:headers) { {} }
8
7
  let(:session) { Clearance::Session.new(env_without_remember_token) }
9
8
  let(:user) { create(:user) }
10
9
 
@@ -35,9 +34,63 @@ describe Clearance::Session do
35
34
  Clearance.configuration.cookie_name = "custom_cookie_name"
36
35
 
37
36
  session.sign_in user
38
- session.add_cookie_to_headers(headers)
37
+ session.add_cookie_to_headers
39
38
 
40
- expect(headers["Set-Cookie"]).to match(/custom_cookie_name=.+;/)
39
+ expect(remember_token_cookie(session, "custom_cookie_name")).to be_present
40
+ end
41
+ end
42
+
43
+ context "with signed cookies == false" do
44
+ it "uses cookies.signed" do
45
+ Clearance.configuration.signed_cookie = true
46
+
47
+ cookie_jar = {}
48
+ expect(session).to receive(:cookies).and_return(cookie_jar)
49
+ expect(cookie_jar).to receive(:signed).and_return(cookie_jar)
50
+
51
+ session.sign_in user
52
+ end
53
+ end
54
+
55
+ context "with signed cookies == true" do
56
+ it "uses cookies.signed" do
57
+ Clearance.configuration.signed_cookie = true
58
+
59
+ cookie_jar = {}
60
+ expect(session).to receive(:cookies).and_return(cookie_jar)
61
+ expect(cookie_jar).to receive(:signed).and_return(cookie_jar)
62
+
63
+ session.sign_in user
64
+ end
65
+ end
66
+
67
+ context "with signed cookies == :migrate" do
68
+ before do
69
+ Clearance.configuration.signed_cookie = :migrate
70
+ end
71
+
72
+ context "signed cookie exists" do
73
+ it "uses cookies.signed[remember_token]" do
74
+ cookie_jar = { "remember_token" => "signed cookie" }
75
+ expect(session).to receive(:cookies).and_return(cookie_jar)
76
+ expect(cookie_jar).to receive(:signed).and_return(cookie_jar)
77
+
78
+ session.sign_in user
79
+ end
80
+ end
81
+
82
+ context "signed cookie does not exist yet" do
83
+ it "uses cookies[remember_token] instead" do
84
+ cookie_jar = { "remember_token" => "signed cookie" }
85
+ # first call will try to get the signed cookie
86
+ expect(session).to receive(:cookies).and_return(cookie_jar)
87
+ # ... but signed_cookie doesn't exist
88
+ expect(cookie_jar).to receive(:signed).and_return({})
89
+ # then it attempts to retrieve the unsigned cookie
90
+ expect(session).to receive(:cookies).and_return(cookie_jar)
91
+
92
+ session.sign_in user
93
+ end
41
94
  end
42
95
  end
43
96
 
@@ -157,9 +210,9 @@ describe Clearance::Session do
157
210
  end
158
211
 
159
212
  it 'sets a httponly cookie' do
160
- session.add_cookie_to_headers(headers)
213
+ session.add_cookie_to_headers
161
214
 
162
- expect(headers['Set-Cookie']).to match(/remember_token=.+; HttpOnly/)
215
+ expect(remember_token_cookie(session)[:httponly]).to be_truthy
163
216
  end
164
217
  end
165
218
 
@@ -170,9 +223,9 @@ describe Clearance::Session do
170
223
  end
171
224
 
172
225
  it 'sets a standard cookie' do
173
- session.add_cookie_to_headers(headers)
226
+ session.add_cookie_to_headers
174
227
 
175
- expect(headers['Set-Cookie']).not_to match(/remember_token=.+; HttpOnly/)
228
+ expect(remember_token_cookie(session)[:httponly]).to be_falsey
176
229
  end
177
230
  end
178
231
 
@@ -183,9 +236,9 @@ describe Clearance::Session do
183
236
  end
184
237
 
185
238
  it "sets a same-site cookie" do
186
- session.add_cookie_to_headers(headers)
239
+ session.add_cookie_to_headers
187
240
 
188
- expect(headers["Set-Cookie"]).to match(/remember_token=.+; SameSite/)
241
+ expect(remember_token_cookie(session)[:same_site]).to eq(:lax)
189
242
  end
190
243
  end
191
244
 
@@ -195,9 +248,9 @@ describe Clearance::Session do
195
248
  end
196
249
 
197
250
  it "sets a standard cookie" do
198
- session.add_cookie_to_headers(headers)
251
+ session.add_cookie_to_headers
199
252
 
200
- expect(headers["Set-Cookie"]).to_not match(/remember_token=.+; SameSite/)
253
+ expect(remember_token_cookie(session)[:same_site]).to be_nil
201
254
  end
202
255
  end
203
256
 
@@ -205,15 +258,11 @@ describe Clearance::Session do
205
258
  context 'default configuration' do
206
259
  it 'is set to 1 year from now' do
207
260
  user = double("User", remember_token: "123abc")
208
- headers = {}
209
261
  session = Clearance::Session.new(env_without_remember_token)
210
262
  session.sign_in user
211
- session.add_cookie_to_headers headers
263
+ session.add_cookie_to_headers
212
264
 
213
- expect(headers).to set_cookie(
214
- 'remember_token',
215
- user.remember_token, 1.year.from_now
216
- )
265
+ expect(remember_token_cookie(session)[:expires]).to eq(1.year.from_now)
217
266
  end
218
267
  end
219
268
 
@@ -225,18 +274,14 @@ describe Clearance::Session do
225
274
  end
226
275
  with_custom_expiration expires_at do
227
276
  user = double("User", remember_token: "123abc")
228
- headers = {}
229
277
  environment = env_with_cookies(remember_me: 'true')
230
278
  session = Clearance::Session.new(environment)
231
279
  session.sign_in user
232
- session.add_cookie_to_headers headers
233
-
234
- expect(headers).to set_cookie(
235
- 'remember_token',
236
- user.remember_token,
237
- remembered_expires
238
- )
239
-
280
+ session.add_cookie_to_headers
281
+ expect(remember_token_cookie(session)[:expires]).to \
282
+ eq(remembered_expires)
283
+ expect(remember_token_cookie(session)[:value]).to \
284
+ eq(user.remember_token)
240
285
  end
241
286
  end
242
287
  end
@@ -249,9 +294,9 @@ describe Clearance::Session do
249
294
  end
250
295
 
251
296
  it 'sets a standard cookie' do
252
- session.add_cookie_to_headers(headers)
297
+ session.add_cookie_to_headers
253
298
 
254
- expect(headers['Set-Cookie']).not_to match(/remember_token=.+; secure/)
299
+ expect(remember_token_cookie(session)[:secure]).to be_falsey
255
300
  end
256
301
  end
257
302
 
@@ -262,9 +307,9 @@ describe Clearance::Session do
262
307
  end
263
308
 
264
309
  it 'sets a secure cookie' do
265
- session.add_cookie_to_headers(headers)
310
+ session.add_cookie_to_headers
266
311
 
267
- expect(headers['Set-Cookie']).to match(/remember_token=.+; secure/)
312
+ expect(remember_token_cookie(session)[:secure]).to be_truthy
268
313
  end
269
314
  end
270
315
  end
@@ -280,9 +325,9 @@ describe Clearance::Session do
280
325
  let(:cookie_domain) { ".example.com" }
281
326
 
282
327
  it "sets a standard cookie" do
283
- session.add_cookie_to_headers(headers)
328
+ session.add_cookie_to_headers
284
329
 
285
- expect(headers['Set-Cookie']).to match(/domain=\.example\.com; path/)
330
+ expect(remember_token_cookie(session)[:domain]).to eq(cookie_domain)
286
331
  end
287
332
  end
288
333
 
@@ -290,9 +335,9 @@ describe Clearance::Session do
290
335
  let(:cookie_domain) { lambda { |_r| ".example.com" } }
291
336
 
292
337
  it "sets a standard cookie" do
293
- session.add_cookie_to_headers(headers)
338
+ session.add_cookie_to_headers
294
339
 
295
- expect(headers['Set-Cookie']).to match(/domain=\.example\.com; path/)
340
+ expect(remember_token_cookie(session)[:domain]).to eq(".example.com")
296
341
  end
297
342
  end
298
343
  end
@@ -301,9 +346,9 @@ describe Clearance::Session do
301
346
  before { session.sign_in(user) }
302
347
 
303
348
  it 'sets a standard cookie' do
304
- session.add_cookie_to_headers(headers)
349
+ session.add_cookie_to_headers
305
350
 
306
- expect(headers["Set-Cookie"]).not_to match(/domain=.+; path/)
351
+ expect(remember_token_cookie(session)[:domain]).to be_nil
307
352
  end
308
353
  end
309
354
  end
@@ -313,9 +358,9 @@ describe Clearance::Session do
313
358
  before { session.sign_in(user) }
314
359
 
315
360
  it 'sets a standard cookie' do
316
- session.add_cookie_to_headers(headers)
361
+ session.add_cookie_to_headers
317
362
 
318
- expect(headers["Set-Cookie"]).to_not match(/domain=.+; path/)
363
+ expect(remember_token_cookie(session)[:domain]).to be_nil
319
364
  end
320
365
  end
321
366
 
@@ -326,18 +371,17 @@ describe Clearance::Session do
326
371
  end
327
372
 
328
373
  it 'sets a standard cookie' do
329
- session.add_cookie_to_headers(headers)
374
+ session.add_cookie_to_headers
330
375
 
331
- expect(headers['Set-Cookie']).to match(/path=\/user; expires/)
376
+ expect(remember_token_cookie(session)[:path]).to eq("/user")
332
377
  end
333
378
  end
334
379
  end
335
380
 
336
381
  it 'does not set a remember token when signed out' do
337
- headers = {}
338
382
  session = Clearance::Session.new(env_without_remember_token)
339
- session.add_cookie_to_headers headers
340
- expect(headers["Set-Cookie"]).to be nil
383
+ session.add_cookie_to_headers
384
+ expect(remember_token_cookie(session)).to be_nil
341
385
  end
342
386
 
343
387
  describe "#sign_out" do
@@ -399,6 +443,16 @@ describe Clearance::Session do
399
443
  end
400
444
  end
401
445
 
446
+ # a bit of a hack to get the cookies that ActionDispatch sets inside session
447
+ def remember_token_cookie(session, cookie_name = "remember_token")
448
+ cookies = session.send(:cookies)
449
+ # see https://stackoverflow.com/a/21315095
450
+ set_cookies = cookies.instance_eval <<-RUBY, __FILE__, __LINE__ + 1
451
+ @set_cookies
452
+ RUBY
453
+ set_cookies[cookie_name]
454
+ end
455
+
402
456
  def env_with_cookies(cookies)
403
457
  Rack::MockRequest.env_for '/', 'HTTP_COOKIE' => serialize_cookies(cookies)
404
458
  end
@@ -66,6 +66,34 @@ describe Clearance::Configuration do
66
66
  end
67
67
  end
68
68
 
69
+ context "when signed_cookie is set to true" do
70
+ it "returns true" do
71
+ Clearance.configure { |config| config.signed_cookie = true }
72
+ expect(Clearance.configuration.signed_cookie).to eq true
73
+ end
74
+ end
75
+
76
+ context "when signed_cookie is not specified" do
77
+ it "defaults to false" do
78
+ expect(Clearance.configuration.signed_cookie).to eq false
79
+ end
80
+ end
81
+
82
+ context "when signed_cookie is set to :migrate" do
83
+ it "returns :migrate" do
84
+ Clearance.configure { |config| config.signed_cookie = :migrate }
85
+ expect(Clearance.configuration.signed_cookie).to eq :migrate
86
+ end
87
+ end
88
+
89
+ context "when signed_cookie is set to an unexpected value" do
90
+ it "returns :migrate" do
91
+ expect {
92
+ Clearance.configure { |config| config.signed_cookie = "unknown" }
93
+ }.to raise_exception(RuntimeError)
94
+ end
95
+ end
96
+
69
97
  context "when no redirect URL specified" do
70
98
  it 'returns "/" as redirect URL' do
71
99
  expect(Clearance::Configuration.new.redirect_url).to eq "/"
@@ -147,9 +147,15 @@ describe Clearance::Generators::InstallGenerator, :generator do
147
147
  end
148
148
 
149
149
  def preserve_original_primary_key_type_setting
150
- original = Rails.configuration.generators.active_record[:primary_key_type]
150
+ active_record = Rails.configuration.generators.active_record
151
+ active_record ||= Rails.configuration.generators.options[:active_record]
152
+ original = active_record[:primary_key_type]
153
+
151
154
  yield
152
- Rails.configuration.generators.active_record[:primary_key_type] = original
155
+
156
+ Rails.application.config.generators do |g|
157
+ g.orm :active_record, primary_key_type: original
158
+ end
153
159
  end
154
160
 
155
161
  def contain_models_inherit_from
@@ -55,4 +55,37 @@ describe ClearanceMailer do
55
55
  text: I18n.t("clearance_mailer.change_password.link_text")
56
56
  )
57
57
  end
58
+
59
+ context "when using a custom model" do
60
+ it "contains a link for a custom model" do
61
+ define_people_routes
62
+ Person = Class.new(User)
63
+ person = Person.new(email: "person@example.com", password: "password")
64
+
65
+ person.forgot_password!
66
+ host = ActionMailer::Base.default_url_options[:host]
67
+ link = "http://#{host}/people/#{person.id}/password/edit" \
68
+ "?token=#{person.confirmation_token}"
69
+
70
+ email = ClearanceMailer.change_password(person)
71
+
72
+ expect(email.text_part.body).to include(link)
73
+ expect(email.html_part.body).to include(link)
74
+
75
+ Object.send(:remove_const, :Person)
76
+ Rails.application.reload_routes!
77
+ end
78
+
79
+ def define_people_routes
80
+ Rails.application.routes.draw do
81
+ resources :people, controller: "clearance/users", only: :create do
82
+ resource(
83
+ :password,
84
+ controller: "clearance/passwords",
85
+ only: %i[edit update],
86
+ )
87
+ end
88
+ end
89
+ end
90
+ end
58
91
  end
@@ -59,7 +59,7 @@ describe User do
59
59
  User.authenticate("bad_email@example.com", password)
60
60
  end
61
61
 
62
- expect(user_does_not_exist_time). to be_within(0.001).of(user_exists_time)
62
+ expect(user_does_not_exist_time). to be_within(0.01).of(user_exists_time)
63
63
  end
64
64
 
65
65
  it "takes the same amount of time to fail authentication regardless of whether user exists" do
@@ -73,7 +73,7 @@ describe User do
73
73
  User.authenticate("bad_email@example.com", "bad_password")
74
74
  end
75
75
 
76
- expect(user_does_not_exist_time). to be_within(0.001).of(user_exists_time)
76
+ expect(user_does_not_exist_time). to be_within(0.01).of(user_exists_time)
77
77
  end
78
78
 
79
79
  it "is retrieved via a case-insensitive search" do
@@ -4,6 +4,17 @@ Clearance.configure do |config|
4
4
  # need an empty block to initialize the configuration object
5
5
  end
6
6
 
7
+ # NOTE: to run the entire suite with signed cookies
8
+ # you can set the signed_cookie default to true
9
+ # and run all specs.
10
+ # However, to fake the actual signing process you
11
+ # can monkey-patch ActionDispatch so signed cookies
12
+ # behave like normal ones
13
+ #
14
+ # class ActionDispatch::Cookies::CookieJar
15
+ # def signed; self; end
16
+ # end
17
+
7
18
  module Clearance
8
19
  module Test
9
20
  module Redirects
@@ -1,11 +1,13 @@
1
1
  module RememberTokenHelpers
2
2
  def request_with_remember_token(remember_token)
3
- cookies = {
4
- 'action_dispatch.cookies' => {
5
- Clearance.configuration.cookie_name => remember_token
6
- }
7
- }
8
- env = { clearance: Clearance::Session.new(cookies) }
3
+ cookies = ActionDispatch::Request.new({}).cookie_jar
4
+ if Clearance.configuration.signed_cookie
5
+ cookies.signed[Clearance.configuration.cookie_name] = remember_token
6
+ else
7
+ cookies[Clearance.configuration.cookie_name] = remember_token
8
+ end
9
+
10
+ env = { clearance: Clearance::Session.new(cookies.request.env) }
9
11
  Rack::Request.new env
10
12
  end
11
13
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: clearance
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.3.0
4
+ version: 2.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dan Croak
@@ -25,7 +25,7 @@ authors:
25
25
  autorequire:
26
26
  bindir: bin
27
27
  cert_chain: []
28
- date: 2020-08-14 00:00:00.000000000 Z
28
+ date: 2021-03-05 00:00:00.000000000 Z
29
29
  dependencies:
30
30
  - !ruby/object:Gem::Dependency
31
31
  name: bcrypt
@@ -145,8 +145,8 @@ extra_rdoc_files:
145
145
  - README.md
146
146
  files:
147
147
  - ".erb-lint.yml"
148
+ - ".github/workflows/tests.yml"
148
149
  - ".gitignore"
149
- - ".travis.yml"
150
150
  - ".yardopts"
151
151
  - Appraisals
152
152
  - CONTRIBUTING.md
@@ -184,6 +184,7 @@ files:
184
184
  - gemfiles/rails_5.1.gemfile
185
185
  - gemfiles/rails_5.2.gemfile
186
186
  - gemfiles/rails_6.0.gemfile
187
+ - gemfiles/rails_6.1.gemfile
187
188
  - lib/clearance.rb
188
189
  - lib/clearance/authentication.rb
189
190
  - lib/clearance/authorization.rb
data/.travis.yml DELETED
@@ -1,28 +0,0 @@
1
- cache: bundler
2
-
3
- language:
4
- - ruby
5
-
6
- rvm:
7
- - 2.4.9
8
- - 2.5.7
9
- - 2.6.5
10
- - 2.7.0
11
-
12
- gemfile:
13
- - gemfiles/rails_5.0.gemfile
14
- - gemfiles/rails_5.1.gemfile
15
- - gemfiles/rails_5.2.gemfile
16
- - gemfiles/rails_6.0.gemfile
17
-
18
- install:
19
- - "bin/setup"
20
-
21
- branches:
22
- only:
23
- - master
24
-
25
- matrix:
26
- exclude:
27
- - rvm: 2.4.9
28
- gemfile: gemfiles/rails_6.0.gemfile