signinable 2.0.11 → 2.0.14

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 +25 -4
  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 +121 -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 +20422 -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 +213 -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 +53 -14
  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,262 @@
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)
29
+ end
30
+
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
+ )
39
+ end
40
+
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)
23
45
  end
24
46
 
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)
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
28
52
  end
29
53
 
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
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
75
143
  end
144
+ end
76
145
 
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)
146
+ context 'when jwt has not expired' do
147
+ before(:each) do
148
+ sign_in_user(user, credentials)
149
+ end
150
+
151
+ it 'returns nil if user not found' do
152
+ allow(described_class).to receive(:find_by).and_return(nil)
153
+ expect(described_class.authenticate_with_token(user.jwt, *credentials)).to be_nil
154
+ end
155
+
156
+ it 'returns user' do
157
+ expect(described_class.authenticate_with_token(user.jwt, *credentials)).to eq(user)
158
+ end
159
+
160
+ it 'returns jwt with user' do
161
+ expect(described_class.authenticate_with_token(user.jwt, *credentials).jwt).to eq(user.jwt)
162
+ end
163
+
164
+ it 'does not update refresh token' do
165
+ allow(described_class).to receive(:refresh_jwt)
166
+ described_class.authenticate_with_token(user.jwt, *credentials)
167
+ expect(described_class).not_to have_received(:refresh_jwt)
85
168
  end
86
169
  end
87
170
 
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)
171
+ context 'when jwt has expired' do
172
+ before(:each) do
173
+ sign_in_user(user, credentials)
174
+ Timecop.travel(Time.zone.now + described_class.jwt_exp)
94
175
  end
95
176
 
96
- it "valid token" do
97
- signin = sign_in_user(@user, @credentials)
98
- User.authenticate_with_token(signin.token, *@credentials).should eq(@user)
177
+ it 'does not do user lookup' do
178
+ allow(described_class).to receive(:find_by)
179
+ described_class.authenticate_with_token(user.jwt, *credentials)
180
+ expect(described_class).not_to have_received(:find_by)
99
181
  end
100
182
 
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
183
+ it 'calls for refresh token' do
184
+ allow(described_class).to receive(:refresh_jwt)
185
+ described_class.authenticate_with_token(user.jwt, *credentials)
186
+ expect(described_class).to have_received(:refresh_jwt)
106
187
  end
107
188
  end
189
+ end
108
190
 
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)
191
+ describe '.refresh_jwt' do
192
+ context 'when jwt is invalid' do
193
+ it 'returns nil' do
194
+ expect(described_class.refresh_jwt('blablabla', *credentials)).to be_nil
116
195
  end
196
+ end
117
197
 
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
198
+ it 'returns nil when signin not found' do
199
+ jwt = described_class.generate_jwt('blablabla', 0)
200
+ expect(described_class.refresh_jwt(jwt, *credentials)).to be_nil
201
+ end
202
+
203
+ it 'returns nil when signin expired' do
204
+ sign_in_user(user, credentials)
205
+ signin = user.last_signin
206
+ Timecop.travel(Time.zone.now + described_class.refresh_exp)
207
+ expect(described_class.refresh_jwt(user.jwt, *credentials)).to be_nil
208
+ end
209
+
210
+ context 'when has no restrictions' do
211
+ %i[ip user_agent].each do |c|
212
+ it "allows signin when #{c} changed" do
213
+ sign_in_user(user, credentials)
214
+ expect(described_class.refresh_jwt(user.jwt, *other_credentials)).to eq(user)
215
+ end
122
216
  end
217
+ end
123
218
 
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
219
+ context 'when has restrictions' do
220
+ %i[ip user_agent].each do |c|
221
+ it "forbids signin when #{c} changed" do
222
+ allow(User).to receive(:signin_restrictions).and_return([c])
223
+ sign_in_user(user, credentials)
224
+ expect(described_class.refresh_jwt(user.jwt, *other_credentials)).to be_nil
129
225
  end
130
226
  end
131
227
  end
228
+
229
+ it 'renews signin' do
230
+ sign_in_user(user, credentials)
231
+ signin = user.last_signin
232
+ allow(signin).to receive(:renew!)
233
+ allow(Signin).to receive(:find_by).with(token: signin.token).and_return(signin)
234
+
235
+ described_class.refresh_jwt(user.jwt, *credentials)
236
+ expect(signin).to have_received(:renew!).with(period: described_class.expiration_period, ip: credentials[0],
237
+ user_agent: credentials[1], refresh_token: true)
238
+ end
239
+
240
+ it 'assigns new jwt' do
241
+ sign_in_user(user, credentials)
242
+ signin = user.last_signin
243
+ allow(user).to receive(:jwt=)
244
+ allow(signin).to receive(:signinable).and_return(user)
245
+ allow(Signin).to receive(:find_by).with(token: signin.token).and_return(signin)
246
+ allow(described_class).to receive(:generate_jwt).and_return('bla')
247
+
248
+ described_class.refresh_jwt(user.jwt, *credentials)
249
+ expect(user).to have_received(:jwt=).with('bla')
250
+ end
251
+
252
+ it 'regenerates jwt' do
253
+ sign_in_user(user, credentials)
254
+ signin = user.last_signin
255
+ allow(described_class).to receive(:generate_jwt)
256
+
257
+ described_class.refresh_jwt(user.jwt, *credentials)
258
+ signin.reload
259
+ expect(described_class).to have_received(:generate_jwt).with(signin.token, signin.signinable_id)
260
+ end
132
261
  end
133
262
  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)