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
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5874be18901fc2c73dc44f49b54e847a9837b3d735ba4bb5c5cb285df08ee811
|
4
|
+
data.tar.gz: b87d026e0221c8684ae4fc1576743fd2cd782fa3a145dbdc814c3ea6d332733b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 237c7de3fef5bc654629f346468beb3df87a3b707988858233d56d224c874be405ef7b4bc32d94e0ec3a48248aaeaae55fe7107d595832497a0d0fd849549d45
|
7
|
+
data.tar.gz: ec0357a307c90c515770f5de1be6457da1cb021fc0bd2dd9fba0a373cd065a32d2236807f58c2a9e68039d6bf2bd19cabb91987e6d1c1b660188cfde94fc7909
|
data/app/models/signin.rb
CHANGED
@@ -10,12 +10,16 @@ class Signin < ActiveRecord::Base
|
|
10
10
|
presence: true,
|
11
11
|
format: { with: Regexp.union(Resolv::IPv4::Regex, Resolv::IPv6::Regex) }
|
12
12
|
|
13
|
+
scope :active, -> { where('expiration_time IS NULL OR expiration_time > ?', Time.zone.now) }
|
14
|
+
|
13
15
|
before_validation on: :create do
|
14
|
-
self.token =
|
16
|
+
self.token = generate_token
|
15
17
|
end
|
16
18
|
|
19
|
+
serialize :custom_data if ActiveRecord::Base.connection.instance_values['config'][:adapter].match('mysql')
|
20
|
+
|
17
21
|
def expire!
|
18
|
-
renew!(0)
|
22
|
+
renew!(period: 0, ip: ip, user_agent: user_agent)
|
19
23
|
end
|
20
24
|
|
21
25
|
def expired?
|
@@ -26,9 +30,16 @@ class Signin < ActiveRecord::Base
|
|
26
30
|
!expiration_time.nil?
|
27
31
|
end
|
28
32
|
|
29
|
-
def renew!(period)
|
30
|
-
|
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)
|
31
38
|
end
|
32
39
|
|
33
|
-
|
40
|
+
private
|
41
|
+
|
42
|
+
def generate_token
|
43
|
+
SecureRandom.urlsafe_base64(rand(50..100))
|
44
|
+
end
|
34
45
|
end
|
data/lib/signinable/engine.rb
CHANGED
@@ -6,30 +6,36 @@ module Signinable
|
|
6
6
|
|
7
7
|
module ClassMethods
|
8
8
|
ALLOWED_RESTRICTIONS = %i[ip user_agent].freeze
|
9
|
-
|
9
|
+
DEFAULT_REFRESH_EXP = 7200
|
10
|
+
DEFAULT_JWT_EXP = 900
|
10
11
|
|
11
|
-
cattr_reader :
|
12
|
+
cattr_reader :refresh_exp
|
12
13
|
cattr_reader :simultaneous_signings
|
13
14
|
cattr_reader :signin_restrictions
|
15
|
+
cattr_reader :jwt_secret
|
16
|
+
cattr_reader :jwt_exp
|
14
17
|
|
15
18
|
def signinable(options = {})
|
16
|
-
self.
|
19
|
+
self.refresh_exp = options.fetch(:refresh_exp, DEFAULT_REFRESH_EXP)
|
17
20
|
self.simultaneous_signings = options.fetch(:simultaneous, true)
|
18
21
|
self.signin_restrictions = options[:restrictions]
|
22
|
+
self.jwt_secret = options.fetch(:jwt_secret)
|
23
|
+
self.jwt_exp = options.fetch(:jwt_exp, DEFAULT_JWT_EXP)
|
19
24
|
|
20
25
|
has_many :signins, as: :signinable, dependent: :destroy
|
26
|
+
|
27
|
+
attr_accessor :jwt
|
21
28
|
end
|
22
29
|
|
23
|
-
def authenticate_with_token(
|
24
|
-
|
30
|
+
def authenticate_with_token(jwt, ip, user_agent, skip_restrictions: [])
|
31
|
+
jwt_payload = extract_jwt_payload(jwt)
|
32
|
+
return refresh_jwt(jwt, ip, user_agent, skip_restrictions: skip_restrictions) unless jwt_payload
|
25
33
|
|
26
|
-
|
27
|
-
return nil
|
28
|
-
return nil unless check_simultaneous_signings(signin)
|
29
|
-
return nil unless check_signin_permission(signin, { ip: ip, user_agent: user_agent }, skip_restrictions)
|
34
|
+
signinable = find_by(primary_key => jwt_payload['signinable_id'])
|
35
|
+
return nil unless signinable
|
30
36
|
|
31
|
-
|
32
|
-
|
37
|
+
signinable.jwt = jwt
|
38
|
+
signinable
|
33
39
|
end
|
34
40
|
|
35
41
|
def check_signin_permission(signin, restrictions_to_check, skip_restrictions)
|
@@ -37,16 +43,57 @@ module Signinable
|
|
37
43
|
end
|
38
44
|
|
39
45
|
def expiration_period
|
40
|
-
return
|
46
|
+
return refresh_exp.call if refresh_exp.respond_to?(:call)
|
47
|
+
|
48
|
+
refresh_exp
|
49
|
+
end
|
41
50
|
|
42
|
-
|
51
|
+
def generate_jwt(refresh_token, signinable_id)
|
52
|
+
JWT.encode(
|
53
|
+
{
|
54
|
+
refresh_token: refresh_token,
|
55
|
+
signinable_id: signinable_id,
|
56
|
+
exp: Time.zone.now.to_i + jwt_exp
|
57
|
+
},
|
58
|
+
jwt_secret,
|
59
|
+
'HS256'
|
60
|
+
)
|
61
|
+
end
|
62
|
+
|
63
|
+
def refresh_jwt(jwt, ip, user_agent, skip_restrictions: [])
|
64
|
+
token = refresh_token_from_jwt(jwt)
|
65
|
+
return nil unless token
|
66
|
+
|
67
|
+
signin = Signin.find_by(token: token)
|
68
|
+
|
69
|
+
return unless signin
|
70
|
+
return nil if signin.expired?
|
71
|
+
return nil unless check_signin_permission(signin, { ip: ip, user_agent: user_agent }, skip_restrictions)
|
72
|
+
|
73
|
+
signin.renew!(period: expiration_period, ip: ip, user_agent: user_agent, refresh_token: true)
|
74
|
+
signin.signinable.jwt = generate_jwt(signin.token, signin.signinable_id)
|
75
|
+
signin.signinable
|
43
76
|
end
|
44
77
|
|
45
78
|
private
|
46
79
|
|
47
|
-
cattr_writer :
|
80
|
+
cattr_writer :refresh_exp
|
48
81
|
cattr_writer :simultaneous_signings
|
49
82
|
cattr_writer :signin_restrictions
|
83
|
+
cattr_writer :jwt_secret
|
84
|
+
cattr_writer :jwt_exp
|
85
|
+
|
86
|
+
def extract_jwt_payload(jwt)
|
87
|
+
JWT.decode(jwt, jwt_secret, true, { algorithm: 'HS256' })[0]
|
88
|
+
rescue JWT::DecodeError
|
89
|
+
nil
|
90
|
+
end
|
91
|
+
|
92
|
+
def refresh_token_from_jwt(jwt)
|
93
|
+
JWT.decode(jwt, jwt_secret, true, { verify_expiration: false, algorithm: 'HS256' })[0]['refresh_token']
|
94
|
+
rescue JWT::DecodeError
|
95
|
+
nil
|
96
|
+
end
|
50
97
|
|
51
98
|
def signin_permitted?(signin, restrictions_to_check, skip_restrictions)
|
52
99
|
restriction_fields = signin_restriction_fields(signin, skip_restrictions)
|
@@ -68,25 +115,22 @@ module Signinable
|
|
68
115
|
end
|
69
116
|
(fields - skip_restrictions) & ALLOWED_RESTRICTIONS
|
70
117
|
end
|
71
|
-
|
72
|
-
def check_simultaneous_signings(signin)
|
73
|
-
return true if simultaneous_signings
|
74
|
-
|
75
|
-
signin == signin.signinable.last_signin
|
76
|
-
end
|
77
118
|
end
|
78
119
|
|
79
120
|
def signin(ip, user_agent, referer, permanent: false, custom_data: {})
|
80
121
|
expires_in = self.class.expiration_period
|
81
122
|
expiration_time = expires_in.zero? || permanent ? nil : expires_in.seconds.from_now
|
82
|
-
Signin.
|
123
|
+
Signin.where(signinable: self).active.map(&:expire!) unless self.class.simultaneous_signings
|
124
|
+
signin = Signin.create!(
|
83
125
|
signinable: self,
|
84
126
|
ip: ip,
|
85
127
|
referer: referer,
|
86
128
|
user_agent: user_agent,
|
87
129
|
expiration_time: expiration_time,
|
88
130
|
custom_data: custom_data
|
89
|
-
)
|
131
|
+
)
|
132
|
+
|
133
|
+
self.jwt = self.class.generate_jwt(signin.token, signin.signinable_id)
|
90
134
|
end
|
91
135
|
|
92
136
|
def signout(token, ip, user_agent, skip_restrictions: [])
|
@@ -105,7 +149,7 @@ module Signinable
|
|
105
149
|
end
|
106
150
|
|
107
151
|
def last_signin
|
108
|
-
signins.last
|
152
|
+
signins.active.last
|
109
153
|
end
|
110
154
|
end
|
111
155
|
end
|
data/lib/signinable/version.rb
CHANGED