rodauth 2.1.0 → 2.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG +56 -0
- data/README.rdoc +14 -0
- data/doc/base.rdoc +3 -1
- data/doc/guides/admin_activation.rdoc +46 -0
- data/doc/guides/already_authenticated.rdoc +10 -0
- data/doc/guides/alternative_login.rdoc +46 -0
- data/doc/guides/create_account_programmatically.rdoc +38 -0
- data/doc/guides/delay_password.rdoc +25 -0
- data/doc/guides/email_only.rdoc +16 -0
- data/doc/guides/i18n.rdoc +26 -0
- data/doc/{internals.rdoc → guides/internals.rdoc} +0 -0
- data/doc/guides/links.rdoc +12 -0
- data/doc/guides/login_return.rdoc +37 -0
- data/doc/guides/password_column.rdoc +25 -0
- data/doc/guides/password_confirmation.rdoc +37 -0
- data/doc/guides/password_requirements.rdoc +30 -0
- data/doc/guides/paths.rdoc +36 -0
- data/doc/guides/query_params.rdoc +9 -0
- data/doc/guides/redirects.rdoc +17 -0
- data/doc/guides/registration_field.rdoc +68 -0
- data/doc/guides/require_mfa.rdoc +30 -0
- data/doc/guides/reset_password_autologin.rdoc +21 -0
- data/doc/guides/status_column.rdoc +28 -0
- data/doc/guides/totp_or_recovery.rdoc +16 -0
- data/doc/jwt_refresh.rdoc +17 -0
- data/doc/login.rdoc +8 -0
- data/doc/login_password_requirements_base.rdoc +3 -0
- data/doc/otp.rdoc +1 -0
- data/doc/password_pepper.rdoc +44 -0
- data/doc/release_notes/2.2.0.txt +39 -0
- data/doc/release_notes/2.3.0.txt +37 -0
- data/doc/release_notes/2.4.0.txt +22 -0
- data/doc/release_notes/2.5.0.txt +20 -0
- data/doc/release_notes/2.6.0.txt +37 -0
- data/doc/verify_login_change.rdoc +1 -0
- data/javascript/webauthn_auth.js +9 -9
- data/javascript/webauthn_setup.js +9 -6
- data/lib/rodauth.rb +13 -9
- data/lib/rodauth/features/active_sessions.rb +5 -7
- data/lib/rodauth/features/audit_logging.rb +2 -0
- data/lib/rodauth/features/base.rb +18 -3
- data/lib/rodauth/features/change_password.rb +1 -1
- data/lib/rodauth/features/close_account.rb +8 -6
- data/lib/rodauth/features/confirm_password.rb +2 -2
- data/lib/rodauth/features/disallow_password_reuse.rb +4 -2
- data/lib/rodauth/features/email_auth.rb +2 -2
- data/lib/rodauth/features/jwt.rb +10 -7
- data/lib/rodauth/features/jwt_cors.rb +15 -15
- data/lib/rodauth/features/jwt_refresh.rb +76 -10
- data/lib/rodauth/features/login.rb +23 -12
- data/lib/rodauth/features/login_password_requirements_base.rb +9 -4
- data/lib/rodauth/features/otp.rb +5 -1
- data/lib/rodauth/features/password_complexity.rb +4 -2
- data/lib/rodauth/features/password_pepper.rb +45 -0
- data/lib/rodauth/features/remember.rb +2 -0
- data/lib/rodauth/features/session_expiration.rb +1 -6
- data/lib/rodauth/features/single_session.rb +1 -1
- data/lib/rodauth/features/sms_codes.rb +0 -1
- data/lib/rodauth/features/two_factor_base.rb +4 -4
- data/lib/rodauth/features/verify_account.rb +10 -6
- data/lib/rodauth/features/verify_account_grace_period.rb +2 -4
- data/lib/rodauth/features/verify_login_change.rb +2 -1
- data/lib/rodauth/features/webauthn.rb +1 -3
- data/lib/rodauth/features/webauthn_login.rb +1 -1
- data/lib/rodauth/migrations.rb +16 -5
- data/lib/rodauth/version.rb +1 -1
- metadata +37 -5
@@ -7,6 +7,9 @@ module Rodauth
|
|
7
7
|
after 'refresh_token'
|
8
8
|
before 'refresh_token'
|
9
9
|
|
10
|
+
auth_value_method :allow_refresh_with_expired_jwt_access_token?, false
|
11
|
+
session_key :jwt_refresh_token_data_session_key, :jwt_refresh_token_data
|
12
|
+
session_key :jwt_refresh_token_hmac_session_key, :jwt_refresh_token_hash
|
10
13
|
auth_value_method :jwt_access_token_key, 'access_token'
|
11
14
|
auth_value_method :jwt_access_token_not_before_period, 5
|
12
15
|
auth_value_method :jwt_access_token_period, 1800
|
@@ -19,23 +22,31 @@ module Rodauth
|
|
19
22
|
auth_value_method :jwt_refresh_token_key_column, :key
|
20
23
|
auth_value_method :jwt_refresh_token_key_param, 'refresh_token'
|
21
24
|
auth_value_method :jwt_refresh_token_table, :account_jwt_refresh_keys
|
25
|
+
translatable_method :jwt_refresh_without_access_token_message, 'no JWT access token provided during refresh'
|
26
|
+
auth_value_method :jwt_refresh_without_access_token_status, 401
|
22
27
|
|
23
28
|
auth_private_methods(
|
24
29
|
:account_from_refresh_token
|
25
30
|
)
|
26
31
|
|
27
32
|
route do |r|
|
33
|
+
@jwt_refresh_route = true
|
34
|
+
before_jwt_refresh_route
|
35
|
+
|
28
36
|
r.post do
|
29
|
-
if
|
30
|
-
|
37
|
+
if !session_value
|
38
|
+
response.status ||= jwt_refresh_without_access_token_status
|
39
|
+
json_response[json_response_error_key] = jwt_refresh_without_access_token_message
|
40
|
+
elsif (refresh_token = param_or_nil(jwt_refresh_token_key_param)) && account_from_refresh_token(refresh_token)
|
31
41
|
transaction do
|
32
42
|
before_refresh_token
|
33
43
|
formatted_token = generate_refresh_token
|
34
44
|
remove_jwt_refresh_token_key(refresh_token)
|
45
|
+
set_jwt_refresh_token_hmac_session_key(formatted_token)
|
46
|
+
json_response[jwt_refresh_token_key] = formatted_token
|
47
|
+
json_response[jwt_access_token_key] = session_jwt
|
35
48
|
after_refresh_token
|
36
49
|
end
|
37
|
-
json_response[jwt_refresh_token_key] = formatted_token
|
38
|
-
json_response[jwt_access_token_key] = session_jwt
|
39
50
|
else
|
40
51
|
json_response[json_response_error_key] = jwt_refresh_invalid_token_message
|
41
52
|
response.status ||= json_response_error_status
|
@@ -52,7 +63,9 @@ module Rodauth
|
|
52
63
|
# JWT login puts the access token in the header.
|
53
64
|
# We put the refresh token in the body.
|
54
65
|
# Note, do not put the access_token in the body here, as the access token content is not yet finalised.
|
55
|
-
json_response['refresh_token'] = generate_refresh_token
|
66
|
+
token = json_response['refresh_token'] = generate_refresh_token
|
67
|
+
|
68
|
+
set_jwt_refresh_token_hmac_session_key(token)
|
56
69
|
end
|
57
70
|
|
58
71
|
def set_jwt_token(token)
|
@@ -80,19 +93,46 @@ module Rodauth
|
|
80
93
|
private
|
81
94
|
|
82
95
|
def _account_from_refresh_token(token)
|
96
|
+
id, token_id, key = _account_refresh_token_split(token)
|
97
|
+
|
98
|
+
unless key &&
|
99
|
+
(id == session_value.to_s) &&
|
100
|
+
(actual = get_active_refresh_token(id, token_id)) &&
|
101
|
+
timing_safe_eql?(key, convert_token_key(actual)) &&
|
102
|
+
jwt_refresh_token_match?(key)
|
103
|
+
return
|
104
|
+
end
|
105
|
+
|
106
|
+
ds = account_ds(id)
|
107
|
+
ds = ds.where(account_status_column=>account_open_status_value) unless skip_status_checks?
|
108
|
+
ds.first
|
109
|
+
end
|
110
|
+
|
111
|
+
def _account_refresh_token_split(token)
|
83
112
|
id, token = split_token(token)
|
84
113
|
return unless id && token
|
85
114
|
|
86
115
|
token_id, key = split_token(token)
|
87
116
|
return unless token_id && key
|
88
117
|
|
89
|
-
|
118
|
+
[id, token_id, key]
|
119
|
+
end
|
90
120
|
|
91
|
-
|
121
|
+
def _jwt_decode_opts
|
122
|
+
if allow_refresh_with_expired_jwt_access_token? && @jwt_refresh_route
|
123
|
+
Hash[super].merge!(:verify_expiration=>false)
|
124
|
+
else
|
125
|
+
super
|
126
|
+
end
|
127
|
+
end
|
92
128
|
|
93
|
-
|
94
|
-
|
95
|
-
|
129
|
+
def jwt_refresh_token_match?(key)
|
130
|
+
# We don't need to match tokens if we are requiring a valid current access token
|
131
|
+
return true unless allow_refresh_with_expired_jwt_access_token?
|
132
|
+
|
133
|
+
# If allowing with expired jwt access token, check the expired session contains
|
134
|
+
# hmac matching submitted and active refresh token.
|
135
|
+
timing_safe_eql?(compute_hmac(session[jwt_refresh_token_data_session_key].to_s + key), session[jwt_refresh_token_hmac_session_key].to_s)
|
96
136
|
end
|
97
137
|
|
98
138
|
def get_active_refresh_token(account_id, token_id)
|
@@ -134,6 +174,32 @@ module Rodauth
|
|
134
174
|
hash
|
135
175
|
end
|
136
176
|
|
177
|
+
def set_jwt_refresh_token_hmac_session_key(token)
|
178
|
+
if allow_refresh_with_expired_jwt_access_token?
|
179
|
+
key = _account_refresh_token_split(token).last
|
180
|
+
data = random_key
|
181
|
+
set_session_value(jwt_refresh_token_data_session_key, data)
|
182
|
+
set_session_value(jwt_refresh_token_hmac_session_key, compute_hmac(data + key))
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
def before_logout
|
187
|
+
if token = param_or_nil(jwt_refresh_token_key_param)
|
188
|
+
if token == 'all'
|
189
|
+
jwt_refresh_token_account_ds(session_value).delete
|
190
|
+
else
|
191
|
+
id, token_id, key = _account_refresh_token_split(token)
|
192
|
+
|
193
|
+
if id && token_id && key && (actual = get_active_refresh_token(session_value, token_id)) && timing_safe_eql?(key, convert_token_key(actual))
|
194
|
+
jwt_refresh_token_account_ds(id).
|
195
|
+
where(jwt_refresh_token_id_column=>token_id).
|
196
|
+
delete
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|
200
|
+
super if defined?(super)
|
201
|
+
end
|
202
|
+
|
137
203
|
def after_close_account
|
138
204
|
jwt_refresh_token_account_ds(account_id).delete
|
139
205
|
super if defined?(super)
|
@@ -23,6 +23,8 @@ module Rodauth
|
|
23
23
|
auth_cached_method :login_form_footer_links
|
24
24
|
auth_cached_method :login_form_footer
|
25
25
|
|
26
|
+
auth_value_methods :login_return_to_requested_location_path
|
27
|
+
|
26
28
|
route do |r|
|
27
29
|
check_already_logged_in
|
28
30
|
before_login_route
|
@@ -62,7 +64,7 @@ module Rodauth
|
|
62
64
|
throw_error_status(login_error_status, password_param, invalid_password_message)
|
63
65
|
end
|
64
66
|
|
65
|
-
|
67
|
+
login('password')
|
66
68
|
end
|
67
69
|
|
68
70
|
set_error_flash login_error_flash unless skip_error_flash
|
@@ -72,13 +74,29 @@ module Rodauth
|
|
72
74
|
|
73
75
|
attr_reader :login_form_header
|
74
76
|
|
77
|
+
def login(auth_type)
|
78
|
+
saved_login_redirect = remove_session_value(login_redirect_session_key)
|
79
|
+
transaction do
|
80
|
+
before_login
|
81
|
+
login_session(auth_type)
|
82
|
+
yield if block_given?
|
83
|
+
after_login
|
84
|
+
end
|
85
|
+
set_notice_flash login_notice_flash
|
86
|
+
redirect(saved_login_redirect || login_redirect)
|
87
|
+
end
|
88
|
+
|
75
89
|
def login_required
|
76
|
-
if login_return_to_requested_location?
|
77
|
-
set_session_value(login_redirect_session_key,
|
90
|
+
if login_return_to_requested_location? && (path = login_return_to_requested_location_path)
|
91
|
+
set_session_value(login_redirect_session_key, path)
|
78
92
|
end
|
79
93
|
super
|
80
94
|
end
|
81
95
|
|
96
|
+
def login_return_to_requested_location_path
|
97
|
+
request.fullpath if request.get?
|
98
|
+
end
|
99
|
+
|
82
100
|
def after_login_entered_during_multi_phase_login
|
83
101
|
set_notice_now_flash need_password_notice_flash
|
84
102
|
if multi_phase_login_forms.length == 1 && (meth = multi_phase_login_forms[0][2])
|
@@ -126,15 +144,8 @@ module Rodauth
|
|
126
144
|
end
|
127
145
|
|
128
146
|
def _login(auth_type)
|
129
|
-
|
130
|
-
|
131
|
-
before_login
|
132
|
-
login_session(auth_type)
|
133
|
-
yield if block_given?
|
134
|
-
after_login
|
135
|
-
end
|
136
|
-
set_notice_flash login_notice_flash
|
137
|
-
redirect(saved_login_redirect || login_redirect)
|
147
|
+
warn("Deprecated #_login method called, use #login instead.")
|
148
|
+
login(auth_type)
|
138
149
|
end
|
139
150
|
end
|
140
151
|
end
|
@@ -4,8 +4,10 @@ module Rodauth
|
|
4
4
|
Feature.define(:login_password_requirements_base, :LoginPasswordRequirementsBase) do
|
5
5
|
translatable_method :already_an_account_with_this_login_message, 'already an account with this login'
|
6
6
|
auth_value_method :login_confirm_param, 'login-confirm'
|
7
|
+
auth_value_method :login_email_regexp, /\A[^,;@ \r\n]+@[^,@; \r\n]+\.[^,@; \r\n]+\z/
|
7
8
|
auth_value_method :login_minimum_length, 3
|
8
9
|
auth_value_method :login_maximum_length, 255
|
10
|
+
translatable_method :login_not_valid_email_message, 'not a valid email address'
|
9
11
|
translatable_method :logins_do_not_match_message, 'logins do not match'
|
10
12
|
auth_value_method :password_confirm_param, 'password-confirm'
|
11
13
|
auth_value_method :password_minimum_length, 6
|
@@ -28,6 +30,7 @@ module Rodauth
|
|
28
30
|
|
29
31
|
auth_methods(
|
30
32
|
:login_meets_requirements?,
|
33
|
+
:login_valid_email?,
|
31
34
|
:password_hash,
|
32
35
|
:password_meets_requirements?,
|
33
36
|
:set_password
|
@@ -104,13 +107,15 @@ module Rodauth
|
|
104
107
|
|
105
108
|
def login_meets_email_requirements?(login)
|
106
109
|
return true unless require_email_address_logins?
|
107
|
-
if login
|
108
|
-
|
109
|
-
end
|
110
|
-
@login_requirement_message = 'not a valid email address'
|
110
|
+
return true if login_valid_email?(login)
|
111
|
+
@login_requirement_message = login_not_valid_email_message
|
111
112
|
return false
|
112
113
|
end
|
113
114
|
|
115
|
+
def login_valid_email?(login)
|
116
|
+
login =~ login_email_regexp
|
117
|
+
end
|
118
|
+
|
114
119
|
def password_meets_length_requirements?(password)
|
115
120
|
return true if password_minimum_length <= password.length
|
116
121
|
@password_requirement_message = password_too_short_message
|
data/lib/rodauth/features/otp.rb
CHANGED
@@ -79,6 +79,7 @@ module Rodauth
|
|
79
79
|
:otp,
|
80
80
|
:otp_exists?,
|
81
81
|
:otp_key,
|
82
|
+
:otp_last_use,
|
82
83
|
:otp_locked_out?,
|
83
84
|
:otp_new_secret,
|
84
85
|
:otp_provisioning_name,
|
@@ -255,7 +256,6 @@ module Rodauth
|
|
255
256
|
def otp_remove
|
256
257
|
otp_key_ds.delete
|
257
258
|
@otp_key = nil
|
258
|
-
super if defined?(super)
|
259
259
|
end
|
260
260
|
|
261
261
|
def otp_add_key
|
@@ -269,6 +269,10 @@ module Rodauth
|
|
269
269
|
update(otp_keys_last_use_column=>Sequel::CURRENT_TIMESTAMP) == 1
|
270
270
|
end
|
271
271
|
|
272
|
+
def otp_last_use
|
273
|
+
convert_timestamp(otp_key_ds.get(otp_keys_last_use_column))
|
274
|
+
end
|
275
|
+
|
272
276
|
def otp_record_authentication_failure
|
273
277
|
otp_key_ds.update(otp_keys_failures_column=>Sequel.identifier(otp_keys_failures_column) + 1)
|
274
278
|
end
|
@@ -26,14 +26,16 @@ module Rodauth
|
|
26
26
|
|
27
27
|
def post_configure
|
28
28
|
super
|
29
|
-
return if
|
29
|
+
return if method(:password_dictionary).owner != Rodauth::PasswordComplexity
|
30
30
|
|
31
31
|
case password_dictionary_file
|
32
32
|
when false
|
33
|
-
|
33
|
+
# nothing
|
34
34
|
when nil
|
35
35
|
default_dictionary_file = '/usr/share/dict/words'
|
36
|
+
# :nocov:
|
36
37
|
if File.file?(default_dictionary_file)
|
38
|
+
# :nocov:
|
37
39
|
words = File.read(default_dictionary_file)
|
38
40
|
end
|
39
41
|
else
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen-string-literal: true
|
2
|
+
|
3
|
+
module Rodauth
|
4
|
+
Feature.define(:password_pepper, :PasswordPepper) do
|
5
|
+
depends :login_password_requirements_base
|
6
|
+
|
7
|
+
auth_value_method :password_pepper, nil
|
8
|
+
auth_value_method :previous_password_peppers, [""]
|
9
|
+
auth_value_method :password_pepper_update?, true
|
10
|
+
|
11
|
+
def password_match?(password)
|
12
|
+
if (result = super) && @previous_pepper_matched && password_pepper_update?
|
13
|
+
set_password(password)
|
14
|
+
end
|
15
|
+
|
16
|
+
result
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def password_hash(password)
|
22
|
+
super(password + password_pepper.to_s)
|
23
|
+
end
|
24
|
+
|
25
|
+
def password_hash_match?(hash, password)
|
26
|
+
return super if password_pepper.nil?
|
27
|
+
|
28
|
+
return true if super(hash, password + password_pepper)
|
29
|
+
|
30
|
+
@previous_pepper_matched = previous_password_peppers.any? do |pepper|
|
31
|
+
super(hash, password + pepper)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def database_function_password_match?(name, hash_id, password, salt)
|
36
|
+
return super if password_pepper.nil?
|
37
|
+
|
38
|
+
return true if super(name, hash_id, password + password_pepper, salt)
|
39
|
+
|
40
|
+
@previous_pepper_matched = previous_password_peppers.any? do |pepper|
|
41
|
+
super(name, hash_id, password + pepper, salt)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -58,7 +58,9 @@ module Rodauth
|
|
58
58
|
if [remember_remember_param_value, remember_forget_param_value, remember_disable_param_value].include?(remember)
|
59
59
|
transaction do
|
60
60
|
before_remember
|
61
|
+
# :nocov:
|
61
62
|
case remember
|
63
|
+
# :nocov:
|
62
64
|
when remember_remember_param_value
|
63
65
|
remember_login
|
64
66
|
when remember_forget_param_value
|
@@ -3,6 +3,7 @@
|
|
3
3
|
module Rodauth
|
4
4
|
Feature.define(:session_expiration, :SessionExpiration) do
|
5
5
|
error_flash "This session has expired, please login again"
|
6
|
+
redirect{require_login_redirect}
|
6
7
|
|
7
8
|
auth_value_method :max_session_lifetime, 86400
|
8
9
|
session_key :session_created_session_key, :session_created_at
|
@@ -11,8 +12,6 @@ module Rodauth
|
|
11
12
|
auth_value_method :session_inactivity_timeout, 1800
|
12
13
|
session_key :session_last_activity_session_key, :last_session_activity_at
|
13
14
|
|
14
|
-
auth_value_methods :session_expiration_redirect
|
15
|
-
|
16
15
|
def check_session_expiration
|
17
16
|
return unless logged_in?
|
18
17
|
|
@@ -43,10 +42,6 @@ module Rodauth
|
|
43
42
|
redirect session_expiration_redirect
|
44
43
|
end
|
45
44
|
|
46
|
-
def session_expiration_redirect
|
47
|
-
require_login_redirect
|
48
|
-
end
|
49
|
-
|
50
45
|
def update_session
|
51
46
|
super
|
52
47
|
t = Time.now.to_i
|
@@ -125,7 +125,7 @@ module Rodauth
|
|
125
125
|
return true if two_factor_authenticated?
|
126
126
|
|
127
127
|
# True if authenticated via single factor and 2nd factor not setup
|
128
|
-
!
|
128
|
+
!uses_two_factor_authentication?
|
129
129
|
end
|
130
130
|
|
131
131
|
def require_authentication
|
@@ -134,7 +134,7 @@ module Rodauth
|
|
134
134
|
# Avoid database query if already authenticated via 2nd factor
|
135
135
|
return if two_factor_authenticated?
|
136
136
|
|
137
|
-
require_two_factor_authenticated if
|
137
|
+
require_two_factor_authenticated if uses_two_factor_authentication?
|
138
138
|
end
|
139
139
|
|
140
140
|
def require_two_factor_setup
|
@@ -208,11 +208,11 @@ module Rodauth
|
|
208
208
|
end
|
209
209
|
|
210
210
|
def _two_factor_setup_links
|
211
|
-
|
211
|
+
[]
|
212
212
|
end
|
213
213
|
|
214
214
|
def _two_factor_remove_links
|
215
|
-
|
215
|
+
[]
|
216
216
|
end
|
217
217
|
|
218
218
|
def _two_factor_remove_all_from_session
|
@@ -70,6 +70,7 @@ module Rodauth
|
|
70
70
|
end
|
71
71
|
|
72
72
|
r.post do
|
73
|
+
verified = false
|
73
74
|
if account_from_login(param(login_param)) && allow_resending_verify_account_email?
|
74
75
|
if verify_account_email_recently_sent?
|
75
76
|
set_redirect_error_flash verify_account_email_recently_sent_error_flash
|
@@ -79,8 +80,11 @@ module Rodauth
|
|
79
80
|
before_verify_account_email_resend
|
80
81
|
if verify_account_email_resend
|
81
82
|
after_verify_account_email_resend
|
83
|
+
verified = true
|
82
84
|
end
|
85
|
+
end
|
83
86
|
|
87
|
+
if verified
|
84
88
|
set_notice_flash verify_account_email_sent_notice_flash
|
85
89
|
else
|
86
90
|
set_redirect_error_status(no_matching_login_error_status)
|
@@ -241,6 +245,12 @@ module Rodauth
|
|
241
245
|
end
|
242
246
|
end
|
243
247
|
|
248
|
+
def setup_account_verification
|
249
|
+
generate_verify_account_key_value
|
250
|
+
create_verify_account_key
|
251
|
+
send_verify_account_email
|
252
|
+
end
|
253
|
+
|
244
254
|
private
|
245
255
|
|
246
256
|
def _login_form_footer_links
|
@@ -272,12 +282,6 @@ module Rodauth
|
|
272
282
|
super
|
273
283
|
end
|
274
284
|
|
275
|
-
def setup_account_verification
|
276
|
-
generate_verify_account_key_value
|
277
|
-
create_verify_account_key
|
278
|
-
send_verify_account_email
|
279
|
-
end
|
280
|
-
|
281
285
|
def verify_account_check_already_logged_in
|
282
286
|
check_already_logged_in
|
283
287
|
end
|