signinable 2.0.12 → 2.0.15

Sign up to get free protection for your applications and to get access to all the features.
@@ -3,50 +3,100 @@
3
3
  require 'rails_helper'
4
4
 
5
5
  describe Signin do
6
- it 'has a valid factory' do
7
- signin = build(:signin)
8
- expect(signin).to be_valid
9
- end
6
+ describe '#expired?' do
7
+ let(:expiration_time) { Time.zone.now + 1.hour }
8
+ let(:signin) { create(:signin, expiration_time: expiration_time) }
10
9
 
11
- context 'is invalid without' do
12
- it 'a token' do
13
- signin = create(:signin)
14
- signin.token = nil
15
- expect(signin).to_not be_valid
10
+ it 'returns false when not expired' do
11
+ expect(signin).to_not be_expired
16
12
  end
17
13
 
18
- it 'an ip' do
19
- expect(build(:signin, ip: nil)).to_not be_valid
14
+ it 'returns true when expired' do
15
+ Timecop.travel(expiration_time) do
16
+ expect(signin).to be_expired
17
+ end
20
18
  end
21
19
  end
22
20
 
23
- it 'should generate token on create' do
24
- signin = create(:signin, token: nil)
25
- expect(signin.token).to_not be_nil
26
- end
27
-
28
- context 'not valid with' do
29
- it 'wrong ip' do
30
- expect(build(:signin, ip: '123')).to_not be_valid
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
31
25
  end
32
- end
33
26
 
34
- it 'should expire' do
35
- Timecop.freeze
36
- expiration_time = Time.zone.now + 1.hour
37
- signin = create(:signin, expiration_time: expiration_time)
38
- Timecop.travel(expiration_time)
39
- expect(signin).to be_expired
40
- Timecop.return
27
+ it 'returns true when expireable' do
28
+ signin = create(:signin, expiration_time: Time.zone.now)
29
+ expect(signin).to be_expireable
30
+ end
41
31
  end
42
32
 
43
- describe '.expire!' do
44
- it 'should set expiration_time to now' do
45
- Timecop.freeze
33
+ describe '#expire!' do
34
+ it 'sets expiration_time to now' do
46
35
  signin = create(:signin, expiration_time: (Time.zone.now + 1.hour))
36
+ allow(signin).to receive(:renew!)
47
37
  signin.expire!
48
- expect(signin).to be_expired
49
- Timecop.return
38
+ expect(signin).to have_received(:renew!).with(period: 0, ip: signin.ip, user_agent: signin.user_agent)
39
+ end
40
+ end
41
+
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
50
100
  end
51
101
  end
52
102
  end
@@ -15,118 +15,248 @@ describe User do
15
15
  Timecop.return
16
16
  end
17
17
 
18
- describe '.signin' do
18
+ describe '#signin' do
19
19
  it 'should create Signin' do
20
20
  expect do
21
21
  sign_in_user(user, credentials)
22
22
  end.to change(Signin, :count).by(1)
23
23
  end
24
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
+
25
41
  it 'should set expiration_time' do
26
- signin = sign_in_user(user, credentials)
27
- expect(signin.expiration_time.to_i).to eq((Time.zone.now + User.signin_expiration).to_i)
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)
28
45
  end
29
46
 
30
47
  it 'should not set expiration_time' do
31
- allow(User).to receive(:signin_expiration).and_return(0)
32
- signin = sign_in_user(user, credentials)
48
+ allow(described_class).to receive(:refresh_exp).and_return(0)
49
+ sign_in_user(user, credentials)
50
+ signin = user.last_signin
33
51
  expect(signin.expiration_time).to be_nil
34
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
76
+ end
35
77
  end
36
78
 
37
- describe '.signout' do
79
+ describe '#signout' do
38
80
  it 'should expire signin' do
39
- signin = sign_in_user(user, credentials)
81
+ sign_in_user(user, credentials)
82
+ signin = user.last_signin
40
83
  sign_out_user(signin, credentials)
41
84
  expect(signin.reload).to be_expired
42
85
  end
43
86
 
44
- context 'should be allowed with' do
87
+ context 'when has no restrictions' do
45
88
  %i[ip user_agent].each do |c|
46
- it "changed #{c} if not restricted" do
47
- signin = sign_in_user(user, credentials)
89
+ it "allows signout when #{c} changes" do
90
+ sign_in_user(user, credentials)
91
+ signin = user.last_signin
48
92
  expect(sign_out_user(signin, credentials)).to be_truthy
49
93
  end
50
94
  end
51
95
  end
52
96
 
53
- context 'should not be allowed with' do
97
+ context 'when has restrictions' do
54
98
  %i[ip user_agent].each do |c|
55
- it "changed #{c} if restricted" do
56
- allow(User).to receive(:signin_restrictions).and_return([c])
57
- signin = sign_in_user(user, credentials)
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
58
103
  expect(sign_out_user(signin, other_credentials)).to be_nil
59
104
  end
60
105
  end
61
106
  end
62
107
  end
63
108
 
64
- describe '#authenticate_with_token' do
65
- context 'expiration_time' do
66
- it 'should be changed after authentication' do
67
- signin = sign_in_user(user, credentials)
68
- old_time = signin.expiration_time
69
- new_time = signin.expiration_time - 1.hour
70
- Timecop.travel(new_time)
71
- User.authenticate_with_token(signin.token, *credentials)
72
- signin.reload
73
- expect(signin.expiration_time.to_i).to 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)
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)
74
158
  end
75
159
 
76
- it 'should not be changed after authentication' do
77
- allow(User).to receive(:signin_expiration).and_return(0)
78
- signin = sign_in_user(user, credentials)
79
- old_time = signin.expiration_time
80
- Timecop.travel(Time.zone.now + 1.hour)
81
- User.authenticate_with_token(signin.token, *credentials)
82
- signin.reload
83
- expect(signin.expiration_time.to_i).to eq(old_time.to_i)
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)
84
168
  end
85
169
  end
86
170
 
87
- context 'should allow signin with' do
88
- it 'not last token if simultaneous is permitted' do
89
- signin1 = sign_in_user(user, credentials)
90
- signin2 = sign_in_user(user, credentials)
91
- expect(User.authenticate_with_token(signin1.token, *credentials)).to eq(user)
92
- expect(User.authenticate_with_token(signin2.token, *credentials)).to 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)
93
175
  end
94
176
 
95
- it 'valid token' do
96
- signin = sign_in_user(user, credentials)
97
- expect(User.authenticate_with_token(signin.token, *credentials)).to 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)
98
181
  end
99
182
 
100
- %i[ip user_agent].each do |c|
101
- it "changed #{c} if not restricted" do
102
- signin = sign_in_user(user, credentials)
103
- expect(User.authenticate_with_token(signin.token, *other_credentials)).to eq(user)
104
- 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)
105
187
  end
106
188
  end
189
+ end
107
190
 
108
- context 'should not allow signin with' do
109
- it 'not last token if simultaneous not permitted' do
110
- allow(User).to receive(:simultaneous_signings).and_return(false)
111
- signin1 = sign_in_user(user, credentials)
112
- signin2 = sign_in_user(user, credentials)
113
- expect(User.authenticate_with_token(signin1.token, *credentials)).to be_nil
114
- expect(User.authenticate_with_token(signin2.token, *credentials)).to 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
115
195
  end
196
+ end
116
197
 
117
- it 'expired token' do
118
- signin = sign_in_user(user, credentials)
119
- user.signout(signin.token, *credentials)
120
- expect(User.authenticate_with_token(signin.token, *credentials)).to 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
121
216
  end
217
+ end
122
218
 
219
+ context 'when has restrictions' do
123
220
  %i[ip user_agent].each do |c|
124
- it "changed #{c} if restricted" do
221
+ it "forbids signin when #{c} changed" do
125
222
  allow(User).to receive(:signin_restrictions).and_return([c])
126
- signin = sign_in_user(user, credentials)
127
- expect(User.authenticate_with_token(signin.token, *other_credentials)).to be_nil
223
+ sign_in_user(user, credentials)
224
+ expect(described_class.refresh_jwt(user.jwt, *other_credentials)).to be_nil
128
225
  end
129
226
  end
130
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
131
261
  end
132
262
  end
@@ -1,8 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  def sign_in_user(user, credentials)
4
- token = user.signin(*credentials, 'referer')
5
- Signin.find_by_token(token)
4
+ user.signin(*credentials, 'referer')
6
5
  end
7
6
 
8
7
  def sign_out_user(signin, credentials)
metadata CHANGED
@@ -1,15 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: signinable
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.12
4
+ version: 2.0.15
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ivan Novozhenets
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-06-14 00:00:00.000000000 Z
11
+ date: 2022-06-16 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: jwt
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 2.4.1
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: 2.4.1
13
27
  - !ruby/object:Gem::Dependency
14
28
  name: rails
15
29
  requirement: !ruby/object:Gem::Requirement