signinable 2.0.10 → 2.0.13

Sign up to get free protection for your applications and to get access to all the features.
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)