signinable 2.0.16 → 3.0.1
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 +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
|