signinable 2.0.12 → 2.0.15

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.
@@ -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