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.
- checksums.yaml +4 -4
- data/app/models/signin.rb +16 -5
- data/lib/signinable/engine.rb +2 -0
- data/lib/signinable/model_additions.rb +67 -23
- data/lib/signinable/version.rb +1 -1
- data/spec/dummy/app/models/user.rb +1 -1
- data/spec/dummy/log/test.log +12230 -0
- data/spec/models/signin_spec.rb +82 -32
- data/spec/models/user_spec.rb +190 -60
- data/spec/support/utilities.rb +1 -2
- metadata +16 -2
data/spec/models/signin_spec.rb
CHANGED
@@ -3,50 +3,100 @@
|
|
3
3
|
require 'rails_helper'
|
4
4
|
|
5
5
|
describe Signin do
|
6
|
-
|
7
|
-
|
8
|
-
|
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
|
-
|
12
|
-
|
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 '
|
19
|
-
|
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
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
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
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
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 '
|
44
|
-
it '
|
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
|
49
|
-
|
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
|
data/spec/models/user_spec.rb
CHANGED
@@ -15,118 +15,248 @@ describe User do
|
|
15
15
|
Timecop.return
|
16
16
|
end
|
17
17
|
|
18
|
-
describe '
|
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
|
-
|
27
|
-
|
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(
|
32
|
-
|
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 '
|
79
|
+
describe '#signout' do
|
38
80
|
it 'should expire signin' do
|
39
|
-
|
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 '
|
87
|
+
context 'when has no restrictions' do
|
45
88
|
%i[ip user_agent].each do |c|
|
46
|
-
it "
|
47
|
-
|
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 '
|
97
|
+
context 'when has restrictions' do
|
54
98
|
%i[ip user_agent].each do |c|
|
55
|
-
it "
|
56
|
-
allow(
|
57
|
-
|
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 '#
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
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 '
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
expect(
|
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 '
|
88
|
-
|
89
|
-
|
90
|
-
|
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 '
|
96
|
-
|
97
|
-
|
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
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
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
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
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
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
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 "
|
221
|
+
it "forbids signin when #{c} changed" do
|
125
222
|
allow(User).to receive(:signin_restrictions).and_return([c])
|
126
|
-
|
127
|
-
expect(
|
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
|
data/spec/support/utilities.rb
CHANGED
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.
|
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-
|
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
|