signinable 2.0.16 → 3.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/app/models/signin.rb +8 -17
- data/db/migrate/20220814152804_change_signinable_id_to_string.rb +12 -0
- data/lib/signinable/model_additions.rb +42 -69
- data/lib/signinable/version.rb +1 -1
- data/spec/dummy/config/application.rb +1 -0
- data/spec/dummy/db/test.sqlite3 +0 -0
- data/spec/dummy/db/test.sqlite3-shm +0 -0
- data/spec/dummy/db/test.sqlite3-wal +0 -0
- data/spec/dummy/log/test.log +9107 -0
- data/spec/models/signin_spec.rb +6 -76
- data/spec/models/user_spec.rb +32 -103
- data/spec/support/utilities.rb +2 -2
- metadata +41 -36
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c4cb3c9ed394555e10d075ec1b4ef7dfc401e81b1914abaff0ac79a34ed8adb1
|
4
|
+
data.tar.gz: d5a98fbe31a1db70c9739eb444bca9f1e070596853a5ef26911839c18d00289f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a2922803d96c5617d587f7a695acc7a6bb902c73a218b0bbbf38f9f1859d9e86da00635a2cd6b6ab1eca6030ac2e46558921749a3204e3fc93ad9d68d5bb6ed0
|
7
|
+
data.tar.gz: aaeed33569ef9f0a6298671076a2aac3496874b02cd4a27f4e505d94d8a4859b9d0c23b5a99a289ceef6c7a9052e0947aaa434da4845aa82bc586b32755637b4
|
data/app/models/signin.rb
CHANGED
@@ -13,33 +13,24 @@ class Signin < ActiveRecord::Base
|
|
13
13
|
scope :active, -> { where('expiration_time IS NULL OR expiration_time > ?', Time.zone.now) }
|
14
14
|
|
15
15
|
before_validation on: :create do
|
16
|
-
self.token = generate_token
|
16
|
+
self.token = self.class.generate_token
|
17
17
|
end
|
18
18
|
|
19
19
|
serialize :custom_data if ActiveRecord::Base.connection.instance_values['config'][:adapter].match('mysql')
|
20
20
|
|
21
21
|
def expire!
|
22
|
-
|
22
|
+
update!(
|
23
|
+
ip: ip,
|
24
|
+
user_agent: user_agent,
|
25
|
+
expiration_time: Time.current
|
26
|
+
)
|
23
27
|
end
|
24
28
|
|
25
29
|
def expired?
|
26
|
-
|
30
|
+
expiration_time.past?
|
27
31
|
end
|
28
32
|
|
29
|
-
def
|
30
|
-
!expiration_time.nil?
|
31
|
-
end
|
32
|
-
|
33
|
-
def renew!(period:, ip:, user_agent:, refresh_token: false)
|
34
|
-
update_hash = { ip: ip, user_agent: user_agent }
|
35
|
-
update_hash[:expiration_time] = Time.zone.now + period if expireable?
|
36
|
-
update_hash[:token] = generate_token if refresh_token
|
37
|
-
update!(update_hash)
|
38
|
-
end
|
39
|
-
|
40
|
-
private
|
41
|
-
|
42
|
-
def generate_token
|
33
|
+
def self.generate_token
|
43
34
|
SecureRandom.urlsafe_base64(rand(50..100))
|
44
35
|
end
|
45
36
|
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
migration_kls = Rails::VERSION::MAJOR > 4 ? ActiveRecord::Migration[5.0] : ActiveRecord::Migration
|
4
|
+
class ChangeSigninableIdToString < migration_kls
|
5
|
+
def self.up
|
6
|
+
change_column :signins, :signinable_id, :string, null: false
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.down
|
10
|
+
change_column :signins, :signinable_id, :integer, null: false
|
11
|
+
end
|
12
|
+
end
|
@@ -9,16 +9,14 @@ module Signinable
|
|
9
9
|
DEFAULT_REFRESH_EXP = 7200
|
10
10
|
DEFAULT_JWT_EXP = 900
|
11
11
|
|
12
|
-
cattr_reader :refresh_exp
|
13
12
|
cattr_reader :simultaneous_signings
|
14
|
-
cattr_reader :signin_restrictions
|
15
13
|
cattr_reader :jwt_secret
|
16
14
|
cattr_reader :jwt_exp
|
15
|
+
cattr_reader :refresh_exp
|
17
16
|
|
18
17
|
def signinable(options = {})
|
19
18
|
self.refresh_exp = options.fetch(:refresh_exp, DEFAULT_REFRESH_EXP)
|
20
19
|
self.simultaneous_signings = options.fetch(:simultaneous, true)
|
21
|
-
self.signin_restrictions = options[:restrictions]
|
22
20
|
self.jwt_secret = options.fetch(:jwt_secret)
|
23
21
|
self.jwt_exp = options.fetch(:jwt_exp, DEFAULT_JWT_EXP)
|
24
22
|
|
@@ -27,21 +25,19 @@ module Signinable
|
|
27
25
|
attr_accessor :jwt
|
28
26
|
end
|
29
27
|
|
30
|
-
def authenticate_with_token(jwt, ip, user_agent
|
28
|
+
def authenticate_with_token(jwt, ip, user_agent)
|
31
29
|
jwt_payload = extract_jwt_payload(jwt)
|
32
|
-
return
|
30
|
+
return nil unless jwt_payload
|
33
31
|
|
34
|
-
|
32
|
+
jwt = refresh_jwt(jwt_payload[:data], ip, user_agent) if jwt_payload[:expired]
|
33
|
+
|
34
|
+
signinable = find_by(primary_key => jwt_payload[:data]['signinable_id'])
|
35
35
|
return nil unless signinable
|
36
36
|
|
37
37
|
signinable.jwt = jwt
|
38
38
|
signinable
|
39
39
|
end
|
40
40
|
|
41
|
-
def check_signin_permission(signin, restrictions_to_check, skip_restrictions)
|
42
|
-
signin_permitted?(signin, restrictions_to_check, skip_restrictions)
|
43
|
-
end
|
44
|
-
|
45
41
|
def expiration_period
|
46
42
|
return refresh_exp.call if refresh_exp.respond_to?(:call)
|
47
43
|
|
@@ -60,101 +56,78 @@ module Signinable
|
|
60
56
|
)
|
61
57
|
end
|
62
58
|
|
63
|
-
def refresh_jwt(
|
64
|
-
|
65
|
-
|
59
|
+
def refresh_jwt(jwt_payload, ip, user_agent)
|
60
|
+
old_token = jwt_payload['refresh_token']
|
61
|
+
new_token = Signin.generate_token
|
66
62
|
|
67
|
-
|
63
|
+
result = Signin.where(token: old_token)
|
64
|
+
.active
|
65
|
+
.update_all(
|
66
|
+
token: new_token,
|
67
|
+
expiration_time: expiration_period.seconds.from_now,
|
68
|
+
ip: ip,
|
69
|
+
user_agent: user_agent
|
70
|
+
)
|
68
71
|
|
69
|
-
return
|
70
|
-
return nil if signin.expired?
|
71
|
-
return nil unless check_signin_permission(signin, { ip: ip, user_agent: user_agent }, skip_restrictions)
|
72
|
+
return if result.zero?
|
72
73
|
|
73
|
-
|
74
|
-
signin.signinable.jwt = generate_jwt(signin.token, signin.signinable_id)
|
75
|
-
signin.signinable
|
74
|
+
generate_jwt(new_token, jwt_payload['signinable_id'])
|
76
75
|
end
|
77
76
|
|
78
|
-
def
|
79
|
-
|
77
|
+
def extract_jwt_payload(jwt)
|
78
|
+
{
|
79
|
+
data: JWT.decode(jwt, jwt_secret, true, { algorithm: 'HS256' })[0],
|
80
|
+
expired: false
|
81
|
+
}
|
80
82
|
rescue JWT::DecodeError
|
81
|
-
|
83
|
+
begin
|
84
|
+
{
|
85
|
+
data: JWT.decode(jwt, jwt_secret, true, { verify_expiration: false, algorithm: 'HS256' })[0],
|
86
|
+
expired: true
|
87
|
+
}
|
88
|
+
rescue JWT::DecodeError
|
89
|
+
nil
|
90
|
+
end
|
82
91
|
end
|
83
92
|
|
84
93
|
private
|
85
94
|
|
86
95
|
cattr_writer :refresh_exp
|
87
96
|
cattr_writer :simultaneous_signings
|
88
|
-
cattr_writer :signin_restrictions
|
89
97
|
cattr_writer :jwt_secret
|
90
98
|
cattr_writer :jwt_exp
|
91
|
-
|
92
|
-
def extract_jwt_payload(jwt)
|
93
|
-
JWT.decode(jwt, jwt_secret, true, { algorithm: 'HS256' })[0]
|
94
|
-
rescue JWT::DecodeError
|
95
|
-
nil
|
96
|
-
end
|
97
|
-
|
98
|
-
def signin_permitted?(signin, restrictions_to_check, skip_restrictions)
|
99
|
-
restriction_fields = signin_restriction_fields(signin, skip_restrictions)
|
100
|
-
|
101
|
-
restrictions_to_check.slice(*restriction_fields).each do |field, value|
|
102
|
-
return false unless signin.send(field) == value
|
103
|
-
end
|
104
|
-
|
105
|
-
true
|
106
|
-
end
|
107
|
-
|
108
|
-
def signin_restriction_fields(signin, skip_restrictions)
|
109
|
-
fields = if signin_restrictions.respond_to?(:call)
|
110
|
-
signin_restrictions.call(signin.signinable)
|
111
|
-
elsif signin_restrictions.is_a?(Array)
|
112
|
-
signin_restrictions
|
113
|
-
else
|
114
|
-
[]
|
115
|
-
end
|
116
|
-
(fields - skip_restrictions) & ALLOWED_RESTRICTIONS
|
117
|
-
end
|
118
99
|
end
|
119
100
|
|
120
|
-
def signin(ip, user_agent, referer,
|
121
|
-
expires_in = self.class.expiration_period
|
122
|
-
expiration_time = expires_in.zero? || permanent ? nil : expires_in.seconds.from_now
|
101
|
+
def signin(ip, user_agent, referer, custom_data: {})
|
123
102
|
Signin.where(signinable: self).active.map(&:expire!) unless self.class.simultaneous_signings
|
103
|
+
|
124
104
|
signin = Signin.create!(
|
125
105
|
signinable: self,
|
126
106
|
ip: ip,
|
127
107
|
referer: referer,
|
128
108
|
user_agent: user_agent,
|
129
|
-
expiration_time:
|
109
|
+
expiration_time: self.class.expiration_period.seconds.from_now,
|
130
110
|
custom_data: custom_data
|
131
111
|
)
|
132
112
|
|
133
113
|
self.jwt = self.class.generate_jwt(signin.token, signin.signinable_id)
|
134
|
-
end
|
135
114
|
|
136
|
-
|
137
|
-
|
138
|
-
return unless token
|
115
|
+
signin
|
116
|
+
end
|
139
117
|
|
140
|
-
|
118
|
+
def signout(jwt)
|
119
|
+
jwt_payload = self.class.extract_jwt_payload(jwt)
|
120
|
+
return unless jwt_payload
|
121
|
+
return if jwt_payload[:expired]
|
141
122
|
|
123
|
+
signin = Signin.find_by(token: jwt_payload[:data]['refresh_token'])
|
142
124
|
return unless signin
|
143
125
|
return if signin.expired?
|
144
|
-
return unless self.class.check_signin_permission(
|
145
|
-
signin,
|
146
|
-
{ ip: ip, user_agent: user_agent },
|
147
|
-
skip_restrictions
|
148
|
-
)
|
149
126
|
|
150
127
|
signin.expire!
|
151
128
|
|
152
129
|
true
|
153
130
|
end
|
154
|
-
|
155
|
-
def last_signin
|
156
|
-
signins.active.last
|
157
|
-
end
|
158
131
|
end
|
159
132
|
end
|
160
133
|
|
data/lib/signinable/version.rb
CHANGED
data/spec/dummy/db/test.sqlite3
CHANGED
Binary file
|
Binary file
|
File without changes
|