signinable 2.0.10 → 2.0.13

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.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/Rakefile +6 -4
  3. data/app/models/signin.rb +24 -5
  4. data/config/routes.rb +2 -0
  5. data/db/migrate/20140103165607_create_signins.rb +7 -5
  6. data/db/migrate/20180530131006_add_custom_data_to_sigins.rb +5 -5
  7. data/lib/signinable/engine.rb +6 -9
  8. data/lib/signinable/model_additions.rb +117 -51
  9. data/lib/signinable/version.rb +3 -1
  10. data/lib/signinable.rb +3 -1
  11. data/spec/dummy/Rakefile +3 -1
  12. data/spec/dummy/app/models/user.rb +3 -1
  13. data/spec/dummy/bin/bundle +3 -1
  14. data/spec/dummy/bin/rails +3 -1
  15. data/spec/dummy/bin/rake +2 -0
  16. data/spec/dummy/config/application.rb +5 -20
  17. data/spec/dummy/config/boot.rb +5 -3
  18. data/spec/dummy/config/environment.rb +3 -1
  19. data/spec/dummy/config/environments/development.rb +4 -2
  20. data/spec/dummy/config/environments/production.rb +2 -0
  21. data/spec/dummy/config/environments/test.rb +4 -2
  22. data/spec/dummy/config/initializers/backtrace_silencers.rb +1 -0
  23. data/spec/dummy/config/initializers/filter_parameter_logging.rb +2 -0
  24. data/spec/dummy/config/initializers/inflections.rb +1 -0
  25. data/spec/dummy/config/initializers/mime_types.rb +1 -0
  26. data/spec/dummy/config/initializers/secret_token.rb +2 -0
  27. data/spec/dummy/config/initializers/session_store.rb +2 -0
  28. data/spec/dummy/config/initializers/wrap_parameters.rb +2 -0
  29. data/spec/dummy/config/routes.rb +2 -0
  30. data/spec/dummy/config.ru +3 -1
  31. data/spec/dummy/db/development.sqlite3 +0 -0
  32. data/spec/dummy/db/migrate/20140103165606_create_users.rb +3 -1
  33. data/spec/dummy/db/schema.rb +23 -24
  34. data/spec/dummy/db/test.sqlite3 +0 -0
  35. data/spec/dummy/log/development.log +427 -0
  36. data/spec/dummy/log/test.log +20023 -0
  37. data/spec/factories/signins.rb +8 -0
  38. data/spec/factories/users.rb +7 -0
  39. data/spec/models/signin_spec.rb +85 -33
  40. data/spec/models/user_spec.rb +204 -84
  41. data/spec/rails_helper.rb +20 -0
  42. data/spec/spec_helper.rb +11 -12
  43. data/spec/support/utilities.rb +3 -2
  44. metadata +76 -38
  45. data/spec/factories/signin.rb +0 -8
  46. data/spec/factories/user.rb +0 -7
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ FactoryBot.define do
4
+ factory :signin do
5
+ ip { '127.0.0.1' }
6
+ signinable
7
+ end
8
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ FactoryBot.define do
4
+ factory :user, aliases: [:signinable] do
5
+ name { 'test' }
6
+ end
7
+ end
@@ -1,50 +1,102 @@
1
- require 'spec_helper'
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails_helper'
2
4
 
3
5
  describe Signin do
4
- it "has a valid factory" do
5
- signin = FactoryGirl.build(:signin)
6
- signin.should be_valid
7
- end
6
+ describe '#expired?' do
7
+ let(:expiration_time) { Time.zone.now + 1.hour }
8
+ let(:signin) { create(:signin, expiration_time: expiration_time) }
8
9
 
9
- context "is invalid without" do
10
- it "a token" do
11
- signin = FactoryGirl.create(:signin)
12
- signin.token = nil
13
- signin.should_not be_valid
10
+ it 'returns false when not expired' do
11
+ expect(signin).to_not be_expired
14
12
  end
15
13
 
16
- it "an ip" do
17
- FactoryGirl.build(:signin, ip: nil).should_not be_valid
14
+ it 'returns true when expired' do
15
+ Timecop.travel(expiration_time) do
16
+ expect(signin).to be_expired
17
+ end
18
18
  end
19
19
  end
20
20
 
21
- it "should generate token on create" do
22
- signin = FactoryGirl.create(:signin, token: nil)
23
- signin.token.should_not be_nil
24
- end
21
+ describe '#expireable?' do
22
+ it 'returns false when expireable' do
23
+ signin = create(:signin, expiration_time: nil)
24
+ expect(signin).to_not be_expireable
25
+ end
25
26
 
26
- context "not valid with" do
27
- it "wrong ip" do
28
- FactoryGirl.build(:signin, ip: "123").should_not be_valid
27
+ it 'returns true when expireable' do
28
+ signin = create(:signin, expiration_time: Time.zone.now)
29
+ expect(signin).to be_expireable
29
30
  end
30
31
  end
31
32
 
32
- it "should expire" do
33
- Timecop.freeze
34
- expiration_time = Time.zone.now + 1.hour
35
- signin = FactoryGirl.create(:signin, expiration_time: expiration_time)
36
- Timecop.travel(expiration_time)
37
- signin.should be_expired
38
- Timecop.return
33
+ describe '#expire!' do
34
+ it 'sets expiration_time to now' do
35
+ signin = create(:signin, expiration_time: (Time.zone.now + 1.hour))
36
+ allow(signin).to receive(:renew!)
37
+ signin.expire!
38
+ expect(signin).to have_received(:renew!).with(period: 0, ip: signin.ip, user_agent: signin.user_agent)
39
+ end
39
40
  end
40
41
 
41
- describe ".expire!" do
42
- it "should set expiration_time to now" do
43
- Timecop.freeze
44
- signin = FactoryGirl.create(:signin, expiration_time: (Time.zone.now + 1.hour))
45
- signin.expire!
46
- signin.should be_expired
47
- Timecop.return
42
+ describe '#renew!' do
43
+ let(:signin) { create(:signin) }
44
+ let(:attrs) do
45
+ {
46
+ period: 100,
47
+ ip: signin.ip,
48
+ user_agent: signin.user_agent,
49
+ refresh_token: false
50
+ }
51
+ end
52
+
53
+ before(:each) do
54
+ allow(signin).to receive(:update!)
55
+ end
56
+
57
+ it 'updates ip and user_agent' do
58
+ signin.renew!(**attrs)
59
+ expect(signin).to have_received(:update!).with(hash_including(ip: signin.ip, user_agent: signin.user_agent))
60
+ end
61
+
62
+ context 'when expireable' do
63
+ before(:each) do
64
+ allow(signin).to receive(:expireable?).and_return(true)
65
+ end
66
+
67
+ it 'updates expiration_time' do
68
+ Timecop.freeze do
69
+ signin.renew!(**attrs)
70
+ expect(signin).to have_received(:update!).with(hash_including(expiration_time: Time.zone.now + attrs[:period]))
71
+ end
72
+ end
73
+ end
74
+
75
+ context 'when not expireable' do
76
+ before(:each) do
77
+ allow(signin).to receive(:expireable?).and_return(false)
78
+ end
79
+
80
+ it 'does not update expiration_time' do
81
+ signin.renew!(**attrs)
82
+ expect(signin).to have_received(:update!).with(hash_excluding(expiration_time: Time.zone.now + attrs[:period]))
83
+ end
84
+ end
85
+
86
+ context 'when need to refresh_token' do
87
+ it 'updates expiration_time' do
88
+ allow(SecureRandom).to receive(:urlsafe_base64).and_return('bla')
89
+ signin.renew!(**attrs.merge(refresh_token: true))
90
+ expect(signin).to have_received(:update!).with(hash_including(token: 'bla'))
91
+ end
92
+ end
93
+
94
+ context 'when no need to refresh_token' do
95
+ it 'does not update expiration_time' do
96
+ allow(SecureRandom).to receive(:urlsafe_base64).and_return('bla')
97
+ signin.renew!(**attrs)
98
+ expect(signin).to have_received(:update!).with(hash_excluding(token: 'bla'))
99
+ end
48
100
  end
49
101
  end
50
102
  end
@@ -1,133 +1,253 @@
1
- require 'spec_helper'
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails_helper'
2
4
 
3
5
  describe User do
6
+ let(:credentials) { ['127.0.0.1', 'user_agent'] }
7
+ let(:other_credentials) { ['127.0.0.2', 'user_agent2'] }
8
+ let(:user) { create(:user) }
9
+
4
10
  before :each do
5
11
  Timecop.freeze
6
- User.signin_expiration = 2.hours
7
- User.signin_simultaneous = true
8
- User.signin_restrictions = []
9
- @user = FactoryGirl.create(:user)
10
- @credentials = ['127.0.0.1', 'user_agent']
11
- @other_credentials = ['127.0.0.2', 'user_agent2']
12
12
  end
13
13
 
14
14
  after :each do
15
15
  Timecop.return
16
16
  end
17
17
 
18
- describe ".signin" do
19
- it "should create Signin" do
20
- expect {
21
- sign_in_user(@user, @credentials)
22
- }.to change(Signin, :count).by(1)
18
+ describe '#signin' do
19
+ it 'should create Signin' do
20
+ expect do
21
+ sign_in_user(user, credentials)
22
+ end.to change(Signin, :count).by(1)
23
+ end
24
+
25
+ it 'sets jwt on user instance' do
26
+ expect do
27
+ sign_in_user(user, credentials)
28
+ end.to change(user, :jwt).from(nil)
23
29
  end
24
30
 
25
- it "should set expiration_time" do
26
- signin = sign_in_user(@user, @credentials)
27
- signin.expiration_time.to_i.should eq((Time.zone.now + User.signin_expiration).to_i)
31
+ it 'should generate jwt with correct payload' do
32
+ sign_in_user(user, credentials)
33
+ signin = user.last_signin
34
+ payload = JWT.decode(user.jwt, 'test', true, { algorithm: 'HS256' })[0]
35
+ expect(payload).to include(
36
+ 'refresh_token' => signin.token,
37
+ 'signinable_id' => user.id
38
+ )
28
39
  end
29
40
 
30
- it "should not set expiration_time" do
31
- User.signin_expiration = 0
32
- signin = sign_in_user(@user, @credentials)
33
- signin.expiration_time.should be_nil
41
+ it 'should set expiration_time' do
42
+ sign_in_user(user, credentials)
43
+ signin = user.last_signin
44
+ expect(signin.expiration_time.to_i).to eq((Time.zone.now + User.refresh_exp).to_i)
45
+ end
46
+
47
+ it 'should not set expiration_time' do
48
+ allow(described_class).to receive(:refresh_exp).and_return(0)
49
+ sign_in_user(user, credentials)
50
+ signin = user.last_signin
51
+ expect(signin.expiration_time).to be_nil
52
+ end
53
+
54
+ context 'when simultaneous signins enabled' do
55
+ before do
56
+ allow(described_class).to receive(:simultaneous_signings).and_return(true)
57
+ end
58
+
59
+ it 'does not expire active signins' do
60
+ sign_in_user(user, credentials)
61
+ sign_in_user(user, credentials)
62
+ expect(user.signins.active.count).to eq(2)
63
+ end
64
+ end
65
+
66
+ context 'when simultaneous signins disabled' do
67
+ before do
68
+ allow(described_class).to receive(:simultaneous_signings).and_return(false)
69
+ end
70
+
71
+ it 'expires active signins' do
72
+ sign_in_user(user, credentials)
73
+ sign_in_user(user, credentials)
74
+ expect(user.signins.active.count).to eq(1)
75
+ end
34
76
  end
35
77
  end
36
78
 
37
- describe ".signout" do
38
- it "should expire signin" do
39
- signin = sign_in_user(@user, @credentials)
40
- sign_out_user(signin, @credentials)
41
- signin.reload
42
- signin.should be_expired
79
+ describe '#signout' do
80
+ it 'should expire signin' do
81
+ sign_in_user(user, credentials)
82
+ signin = user.last_signin
83
+ sign_out_user(signin, credentials)
84
+ expect(signin.reload).to be_expired
43
85
  end
44
86
 
45
- context "should be allowed with" do
46
- %w{ip user_agent}.each do |c|
47
- it "changed #{c} if not restricted" do
48
- signin = sign_in_user(@user, @credentials)
49
- sign_out_user(signin, @credentials).should be_true
87
+ context 'when has no restrictions' do
88
+ %i[ip user_agent].each do |c|
89
+ it "allows signout when #{c} changes" do
90
+ sign_in_user(user, credentials)
91
+ signin = user.last_signin
92
+ expect(sign_out_user(signin, credentials)).to be_truthy
50
93
  end
51
94
  end
52
95
  end
53
96
 
54
- context "should not be allowed with" do
55
- %w{ip user_agent}.each do |c|
56
- it "changed #{c} if restricted" do
57
- User.signin_restrictions = [c]
58
- signin = sign_in_user(@user, @credentials)
59
- sign_out_user(signin, @other_credentials).should be_nil
97
+ context 'when has restrictions' do
98
+ %i[ip user_agent].each do |c|
99
+ it "forbids signout when #{c} changes" do
100
+ allow(described_class).to receive(:signin_restrictions).and_return([c])
101
+ sign_in_user(user, credentials)
102
+ signin = user.last_signin
103
+ expect(sign_out_user(signin, other_credentials)).to be_nil
60
104
  end
61
105
  end
62
106
  end
63
107
  end
64
108
 
65
- describe "#authenticate_with_token" do
66
- context "expiration_time" do
67
- it "should be changed after authentication" do
68
- signin = sign_in_user(@user, @credentials)
69
- old_time = signin.expiration_time
70
- new_time = signin.expiration_time - 1.hour
71
- Timecop.travel(new_time)
72
- User.authenticate_with_token(signin.token, *@credentials)
73
- signin.reload
74
- signin.expiration_time.to_i.should eq((new_time + User.signin_expiration).to_i)
109
+ describe '#last_signin' do
110
+ it 'retuns nil when no signins' do
111
+ expect(user.last_signin).to be_nil
112
+ end
113
+
114
+ it 'returns last active signin' do
115
+ sign_in_user(user, credentials)
116
+ sign_in_user(user, credentials)
117
+ signin = user.signins.active.last
118
+ sign_in_user(user, credentials)
119
+ user.signins.last.expire!
120
+
121
+ expect(user.last_signin).to eq(signin)
122
+ end
123
+ end
124
+
125
+ describe '.generate_jwt' do
126
+ let(:token) { SecureRandom.urlsafe_base64(rand(50..100)) }
127
+
128
+ it 'sets correct payload' do
129
+ jwt = described_class.generate_jwt(token, user.id)
130
+ payload = JWT.decode(jwt, described_class.jwt_secret, true, { algorithm: 'HS256' })[0]
131
+ expect(payload).to eq(
132
+ 'refresh_token' => token,
133
+ 'signinable_id' => user.id,
134
+ 'exp' => Time.zone.now.to_i + described_class.jwt_exp
135
+ )
136
+ end
137
+ end
138
+
139
+ describe '.authenticate_with_token' do
140
+ context 'when jwt is invalid' do
141
+ it 'returns nil' do
142
+ expect(described_class.authenticate_with_token('blablabla', *credentials)).to be_nil
143
+ end
144
+ end
145
+
146
+ context 'when jwt has not expired' do
147
+ before(:each) do
148
+ sign_in_user(user, credentials)
75
149
  end
76
150
 
77
- it "should not be changed after authentication" do
78
- User.signin_expiration = 0
79
- signin = sign_in_user(@user, @credentials)
80
- old_time = signin.expiration_time
81
- Timecop.travel(Time.zone.now + 1.hour)
82
- User.authenticate_with_token(signin.token, *@credentials)
83
- signin.reload
84
- signin.expiration_time.to_i.should eq(old_time.to_i)
151
+ it 'returns user' do
152
+ expect(described_class.authenticate_with_token(user.jwt, *credentials)).to eq(user)
153
+ end
154
+
155
+ it 'does not update refresh token' do
156
+ allow(described_class).to receive(:refresh_jwt)
157
+ described_class.authenticate_with_token(user.jwt, *credentials)
158
+ expect(described_class).not_to have_received(:refresh_jwt)
85
159
  end
86
160
  end
87
161
 
88
- context "should allow signin with" do
89
- it "not last token if simultaneous is permitted" do
90
- signin1 = sign_in_user(@user, @credentials)
91
- signin2 = sign_in_user(@user, @credentials)
92
- User.authenticate_with_token(signin1.token, *@credentials).should eq(@user)
93
- User.authenticate_with_token(signin2.token, *@credentials).should eq(@user)
162
+ context 'when jwt has expired' do
163
+ before(:each) do
164
+ sign_in_user(user, credentials)
165
+ Timecop.travel(Time.zone.now + described_class.jwt_exp)
94
166
  end
95
167
 
96
- it "valid token" do
97
- signin = sign_in_user(@user, @credentials)
98
- User.authenticate_with_token(signin.token, *@credentials).should eq(@user)
168
+ it 'does not do user lookup' do
169
+ allow(described_class).to receive(:find_by)
170
+ described_class.authenticate_with_token(user.jwt, *credentials)
171
+ expect(described_class).not_to have_received(:find_by)
99
172
  end
100
173
 
101
- %w{ip user_agent}.each do |c|
102
- it "changed #{c} if not restricted" do
103
- signin = sign_in_user(@user, @credentials)
104
- User.authenticate_with_token(signin.token, *@other_credentials).should eq(@user)
105
- end
174
+ it 'calls for refresh token' do
175
+ allow(described_class).to receive(:refresh_jwt)
176
+ described_class.authenticate_with_token(user.jwt, *credentials)
177
+ expect(described_class).to have_received(:refresh_jwt)
106
178
  end
107
179
  end
180
+ end
108
181
 
109
- context "should not allow signin with" do
110
- it "not last token if simultaneous not permitted" do
111
- User.signin_simultaneous = false
112
- signin1 = sign_in_user(@user, @credentials)
113
- signin2 = sign_in_user(@user, @credentials)
114
- User.authenticate_with_token(signin1.token, *@credentials).should be_nil
115
- User.authenticate_with_token(signin2.token, *@credentials).should eq(@user)
182
+ describe '.refresh_jwt' do
183
+ context 'when jwt is invalid' do
184
+ it 'returns nil' do
185
+ expect(described_class.refresh_jwt('blablabla', *credentials)).to be_nil
116
186
  end
187
+ end
188
+
189
+ it 'returns nil when signin not found' do
190
+ jwt = described_class.generate_jwt('blablabla', 0)
191
+ expect(described_class.refresh_jwt(jwt, *credentials)).to be_nil
192
+ end
193
+
194
+ it 'returns nil when signin expired' do
195
+ sign_in_user(user, credentials)
196
+ signin = user.last_signin
197
+ Timecop.travel(Time.zone.now + described_class.refresh_exp)
198
+ expect(described_class.refresh_jwt(user.jwt, *credentials)).to be_nil
199
+ end
117
200
 
118
- it "expired token" do
119
- signin = sign_in_user(@user, @credentials)
120
- @user.signout(signin.token, *@credentials)
121
- User.authenticate_with_token(signin.token, *@credentials).should be_nil
201
+ context 'when has no restrictions' do
202
+ %i[ip user_agent].each do |c|
203
+ it "allows signin when #{c} changed" do
204
+ sign_in_user(user, credentials)
205
+ expect(described_class.refresh_jwt(user.jwt, *other_credentials)).to eq(user)
206
+ end
122
207
  end
208
+ end
123
209
 
124
- %w{ip user_agent}.each do |c|
125
- it "changed #{c} if restricted" do
126
- User.signin_restrictions = [c]
127
- signin = sign_in_user(@user, @credentials)
128
- User.authenticate_with_token(signin.token, *@other_credentials).should be_nil
210
+ context 'when has restrictions' do
211
+ %i[ip user_agent].each do |c|
212
+ it "forbids signin when #{c} changed" do
213
+ allow(User).to receive(:signin_restrictions).and_return([c])
214
+ sign_in_user(user, credentials)
215
+ expect(described_class.refresh_jwt(user.jwt, *other_credentials)).to be_nil
129
216
  end
130
217
  end
131
218
  end
219
+
220
+ it 'renews signin' do
221
+ sign_in_user(user, credentials)
222
+ signin = user.last_signin
223
+ allow(signin).to receive(:renew!)
224
+ allow(Signin).to receive(:find_by).with(token: signin.token).and_return(signin)
225
+
226
+ described_class.refresh_jwt(user.jwt, *credentials)
227
+ expect(signin).to have_received(:renew!).with(period: described_class.expiration_period, ip: credentials[0],
228
+ user_agent: credentials[1], refresh_token: true)
229
+ end
230
+
231
+ it 'assigns new jwt' do
232
+ sign_in_user(user, credentials)
233
+ signin = user.last_signin
234
+ allow(user).to receive(:jwt=)
235
+ allow(signin).to receive(:signinable).and_return(user)
236
+ allow(Signin).to receive(:find_by).with(token: signin.token).and_return(signin)
237
+ allow(described_class).to receive(:generate_jwt).and_return('bla')
238
+
239
+ described_class.refresh_jwt(user.jwt, *credentials)
240
+ expect(user).to have_received(:jwt=).with('bla')
241
+ end
242
+
243
+ it 'regenerates jwt' do
244
+ sign_in_user(user, credentials)
245
+ signin = user.last_signin
246
+ allow(described_class).to receive(:generate_jwt)
247
+
248
+ described_class.refresh_jwt(user.jwt, *credentials)
249
+ signin.reload
250
+ expect(described_class).to have_received(:generate_jwt).with(signin.token, signin.signinable_id)
251
+ end
132
252
  end
133
253
  end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ ENV['RAILS_ENV'] ||= 'test'
4
+
5
+ require File.expand_path('dummy/config/environment', __dir__)
6
+
7
+ require 'spec_helper'
8
+ require 'rspec/rails'
9
+
10
+ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].sort.each { |f| require f }
11
+
12
+ RSpec.configure do |config|
13
+ config.use_transactional_fixtures = true
14
+
15
+ config.infer_spec_type_from_file_location!
16
+
17
+ config.filter_rails_from_backtrace!
18
+
19
+ config.include FactoryBot::Syntax::Methods
20
+ end
data/spec/spec_helper.rb CHANGED
@@ -1,18 +1,17 @@
1
- ENV['RAILS_ENV'] ||= 'test'
1
+ # frozen_string_literal: true
2
2
 
3
- require File.expand_path('../dummy/config/environment', __FILE__)
4
- require 'rspec/rails'
5
- require 'rspec/autorun'
6
- require 'factory_girl_rails'
3
+ require 'factory_bot_rails'
4
+ require 'pry'
7
5
  require 'timecop'
8
6
 
9
- Rails.backtrace_cleaner.remove_silencers!
7
+ RSpec.configure do |config|
8
+ config.expect_with :rspec do |expectations|
9
+ expectations.include_chain_clauses_in_custom_matcher_descriptions = true
10
+ end
10
11
 
11
- Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }
12
+ config.mock_with :rspec do |mocks|
13
+ mocks.verify_partial_doubles = true
14
+ end
12
15
 
13
- RSpec.configure do |config|
14
- config.mock_with :rspec
15
- config.use_transactional_fixtures = false
16
- config.infer_base_class_for_anonymous_controllers = false
17
- config.order = 'random'
16
+ config.shared_context_metadata_behavior = :apply_to_host_groups
18
17
  end
@@ -1,6 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  def sign_in_user(user, credentials)
2
- token = user.signin(*credentials, 'referer')
3
- Signin.find_by_token(token)
4
+ user.signin(*credentials, 'referer')
4
5
  end
5
6
 
6
7
  def sign_out_user(signin, credentials)