rodauth 2.1.0 → 2.6.0
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/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
|