rodauth 2.2.0 → 2.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG +14 -0
- data/doc/jwt_refresh.rdoc +5 -0
- data/doc/login.rdoc +7 -0
- data/doc/login_password_requirements_base.rdoc +3 -0
- data/doc/release_notes/2.3.0.txt +37 -0
- data/lib/rodauth/features/close_account.rb +8 -6
- data/lib/rodauth/features/email_auth.rb +1 -1
- data/lib/rodauth/features/jwt_refresh.rb +10 -4
- data/lib/rodauth/features/login.rb +15 -10
- data/lib/rodauth/features/login_password_requirements_base.rb +9 -4
- data/lib/rodauth/features/session_expiration.rb +1 -6
- data/lib/rodauth/features/webauthn_login.rb +1 -1
- data/lib/rodauth/migrations.rb +16 -5
- data/lib/rodauth/version.rb +1 -1
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4ea58deac2998a11cd0cd4b17545eab5fce6fb0acd7751416fd521cbd77d6add
|
4
|
+
data.tar.gz: c113c1131f5d756fd3aa5c1dbf083f40506b8822fb1c9b67537069c9e8695fba
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1fa2ce1d08a9f09e21d2fb1fe5b71ea3c6fc55181452302a35929229f0fad8c3c5d3d3324d73bee9c69daf0ff7a2d14ddbb731ef994bd3317512627233a69d28
|
7
|
+
data.tar.gz: 7a188457006390730f00bc4a2ea5159c002dba66b300fd993929a5b5a491f2f4be0191c7e7c8e4b9e82b2e709b1a4e8ea8f5d335613f262671e1d68507cc2e26
|
data/CHANGELOG
CHANGED
@@ -1,3 +1,17 @@
|
|
1
|
+
=== 2.3.0 (2020-08-21)
|
2
|
+
|
3
|
+
* Return an error status instead of an invalid access token when trying to refresh JWT without an access token in the jwt_refresh feature (jeremyevans)
|
4
|
+
|
5
|
+
* Allow {create,drop}_database_authentication_functions to work with UUID keys (monorkin, janko) (#117)
|
6
|
+
|
7
|
+
* Add rodauth.login('login_type') for logging in after setting a valid account (janko) (#114)
|
8
|
+
|
9
|
+
* Make new refresh token available to the after_refresh_token hook by setting it in the response first (jeremyevans)
|
10
|
+
|
11
|
+
* Make the jwt_refresh plugin call before_jwt_refresh_route hook (previously the configuration method was ignored) (AlexeyMatskevich) (#110)
|
12
|
+
|
13
|
+
* Add login_email_regexp, login_not_valid_email_message, and log_valid_email? configuration methods (janko) (#107)
|
14
|
+
|
1
15
|
=== 2.2.0 (2020-07-20)
|
2
16
|
|
3
17
|
* Allow removing all jwt_refresh tokens when logging out by providing a value of "all" as the token to remove (jeremyevans)
|
data/doc/jwt_refresh.rdoc
CHANGED
@@ -19,6 +19,9 @@ when logging out, provide the refresh token when submitting the JSON request to
|
|
19
19
|
If you would like to remove all refresh tokens for the account when logging out, provide
|
20
20
|
a value of <tt>all</tt> as the token value.
|
21
21
|
|
22
|
+
When using the refresh token, you must provide a valid access token, as that contains
|
23
|
+
information about the current session, which is used to create the new access token.
|
24
|
+
|
22
25
|
This feature depends on the jwt feature.
|
23
26
|
|
24
27
|
== Auth Value Methods
|
@@ -36,6 +39,8 @@ jwt_refresh_token_key :: Name of the key in the response json holding the refres
|
|
36
39
|
jwt_refresh_token_key_column :: The column name in the +jwt_refresh_token_table+ holding the refresh token key value.
|
37
40
|
jwt_refresh_token_key_param :: Name of parameter in which the refresh token is provided when requesting a new token. Default is +refresh_token+.
|
38
41
|
jwt_refresh_token_table :: Name of the table holding refresh token keys.
|
42
|
+
jwt_refresh_without_access_token_message :: Error message when trying to refresh with providing an access token.
|
43
|
+
jwt_refresh_without_access_token_status :: The HTTP status code to use when trying to refresh without providing an access token.
|
39
44
|
|
40
45
|
== Auth Methods
|
41
46
|
|
data/doc/login.rdoc
CHANGED
@@ -3,6 +3,13 @@
|
|
3
3
|
The login feature implements a login page. It's the most commonly
|
4
4
|
used feature.
|
5
5
|
|
6
|
+
In addition to the auth methods below, it provides a +login+ method that wraps
|
7
|
+
+login_session+, running login hooks and redirecting to the configured
|
8
|
+
location.
|
9
|
+
|
10
|
+
rodauth.account #=> { id: 123, ... }
|
11
|
+
rodauth.login('password') # login the current account
|
12
|
+
|
6
13
|
== Auth Value Methods
|
7
14
|
|
8
15
|
login_additional_form_tags :: HTML fragment containing additional form tags to use on the login form.
|
@@ -9,8 +9,10 @@ already_an_account_with_this_login_message :: The error message to display when
|
|
9
9
|
login_confirm_label :: The label to use for login confirmations.
|
10
10
|
login_confirm_param :: The parameter name to use for login confirmations.
|
11
11
|
login_does_not_meet_requirements_message :: The error message to display when the login does not meet the requirements you have set.
|
12
|
+
login_email_regexp :: The regular expression used to validate whether login is a valid email address.
|
12
13
|
login_maximum_length :: The maximum length for logins, 255 by default.
|
13
14
|
login_minimum_length :: The minimum length for logins, 3 by default.
|
15
|
+
login_not_valid_email_message :: The error message to display when login is not a valid email address.
|
14
16
|
login_too_long_message :: The error message fragment to show if the login is too long.
|
15
17
|
login_too_short_message :: The error message fragment to show if the login is too short.
|
16
18
|
logins_do_not_match_message :: The error message to display when login and login confirmation do not match.
|
@@ -29,6 +31,7 @@ same_as_existing_password_message :: The error message to display when a new pas
|
|
29
31
|
== Auth Methods
|
30
32
|
|
31
33
|
login_meets_requirements?(login) :: Whether the given login meets the requirements. By default, just checks that the login is a valid email address.
|
34
|
+
login_valid_email?(login) :: Whether the login is a valid email address.
|
32
35
|
password_hash(password) :: A hash of the given password.
|
33
36
|
password_meets_requirements?(password) :: Whether the given password meets the requirements. Can be used to implement complexity requirements for passwords.
|
34
37
|
set_password(password) :: Set the password for the current account to the given password.
|
@@ -0,0 +1,37 @@
|
|
1
|
+
= New Features
|
2
|
+
|
3
|
+
* Configuration methods have been added for easier validation of
|
4
|
+
logins when logins must be valid email addresses (the default):
|
5
|
+
|
6
|
+
* login_valid_email?(login) can be used for full control of
|
7
|
+
determining whether the login is valid.
|
8
|
+
|
9
|
+
* login_email_regexp can be used to set the regexp used in the
|
10
|
+
default login_valid_email? check.
|
11
|
+
|
12
|
+
* login_not_valid_email_message can be used to set the field
|
13
|
+
error message if the login is not a valid email. Previously, this
|
14
|
+
value was hardcoded and not translatable.
|
15
|
+
|
16
|
+
* The {create,drop}_database_authentication_functions now work
|
17
|
+
correctly with uuid keys on PostgreSQL. All other parts of
|
18
|
+
Rodauth already worked correctly with uuid keys.
|
19
|
+
|
20
|
+
= Other Improvements
|
21
|
+
|
22
|
+
* The before_jwt_refresh_route hook is now called before the route
|
23
|
+
is taken. Previously, the configuration method had no effect.
|
24
|
+
|
25
|
+
* rodauth.login can now be used by external code to login the current
|
26
|
+
account (the account that rodauth.account returns). This should be
|
27
|
+
passed the authentication type string used to login, such as
|
28
|
+
password.
|
29
|
+
|
30
|
+
* The jwt_refresh route now returns an error for requests where a
|
31
|
+
valid access token for a logged in session is not provided. You
|
32
|
+
can use the jwt_refresh_without_access_token_message and
|
33
|
+
jwt_refresh_without_access_token_status configuration methods
|
34
|
+
to configure the error response.
|
35
|
+
|
36
|
+
* The new refresh token is now available to the after_refresh_token
|
37
|
+
hook by looking in json_response[jwt_refresh_token_key].
|
@@ -33,7 +33,11 @@ module Rodauth
|
|
33
33
|
end
|
34
34
|
|
35
35
|
r.post do
|
36
|
-
|
36
|
+
catch_error do
|
37
|
+
if close_account_requires_password? && !password_match?(param(password_param))
|
38
|
+
throw_error_status(invalid_password_error_status, password_param, invalid_password_message)
|
39
|
+
end
|
40
|
+
|
37
41
|
transaction do
|
38
42
|
before_close_account
|
39
43
|
close_account
|
@@ -46,12 +50,10 @@ module Rodauth
|
|
46
50
|
|
47
51
|
set_notice_flash close_account_notice_flash
|
48
52
|
redirect close_account_redirect
|
49
|
-
else
|
50
|
-
set_response_error_status(invalid_password_error_status)
|
51
|
-
set_field_error(password_param, invalid_password_message)
|
52
|
-
set_error_flash close_account_error_flash
|
53
|
-
close_account_view
|
54
53
|
end
|
54
|
+
|
55
|
+
set_error_flash close_account_error_flash
|
56
|
+
close_account_view
|
55
57
|
end
|
56
58
|
end
|
57
59
|
|
@@ -19,23 +19,29 @@ module Rodauth
|
|
19
19
|
auth_value_method :jwt_refresh_token_key_column, :key
|
20
20
|
auth_value_method :jwt_refresh_token_key_param, 'refresh_token'
|
21
21
|
auth_value_method :jwt_refresh_token_table, :account_jwt_refresh_keys
|
22
|
+
translatable_method :jwt_refresh_without_access_token_message, 'no JWT access token provided during refresh'
|
23
|
+
auth_value_method :jwt_refresh_without_access_token_status, 401
|
22
24
|
|
23
25
|
auth_private_methods(
|
24
26
|
:account_from_refresh_token
|
25
27
|
)
|
26
28
|
|
27
29
|
route do |r|
|
30
|
+
before_jwt_refresh_route
|
31
|
+
|
28
32
|
r.post do
|
29
|
-
if
|
30
|
-
|
33
|
+
if !session_value
|
34
|
+
response.status ||= jwt_refresh_without_access_token_status
|
35
|
+
json_response[json_response_error_key] = jwt_refresh_without_access_token_message
|
36
|
+
elsif (refresh_token = param_or_nil(jwt_refresh_token_key_param)) && account_from_refresh_token(refresh_token)
|
31
37
|
transaction do
|
32
38
|
before_refresh_token
|
33
39
|
formatted_token = generate_refresh_token
|
34
40
|
remove_jwt_refresh_token_key(refresh_token)
|
41
|
+
json_response[jwt_refresh_token_key] = formatted_token
|
42
|
+
json_response[jwt_access_token_key] = session_jwt
|
35
43
|
after_refresh_token
|
36
44
|
end
|
37
|
-
json_response[jwt_refresh_token_key] = formatted_token
|
38
|
-
json_response[jwt_access_token_key] = session_jwt
|
39
45
|
else
|
40
46
|
json_response[json_response_error_key] = jwt_refresh_invalid_token_message
|
41
47
|
response.status ||= json_response_error_status
|
@@ -62,7 +62,7 @@ module Rodauth
|
|
62
62
|
throw_error_status(login_error_status, password_param, invalid_password_message)
|
63
63
|
end
|
64
64
|
|
65
|
-
|
65
|
+
login('password')
|
66
66
|
end
|
67
67
|
|
68
68
|
set_error_flash login_error_flash unless skip_error_flash
|
@@ -72,6 +72,18 @@ module Rodauth
|
|
72
72
|
|
73
73
|
attr_reader :login_form_header
|
74
74
|
|
75
|
+
def login(auth_type)
|
76
|
+
saved_login_redirect = remove_session_value(login_redirect_session_key)
|
77
|
+
transaction do
|
78
|
+
before_login
|
79
|
+
login_session(auth_type)
|
80
|
+
yield if block_given?
|
81
|
+
after_login
|
82
|
+
end
|
83
|
+
set_notice_flash login_notice_flash
|
84
|
+
redirect(saved_login_redirect || login_redirect)
|
85
|
+
end
|
86
|
+
|
75
87
|
def login_required
|
76
88
|
if login_return_to_requested_location?
|
77
89
|
set_session_value(login_redirect_session_key, request.fullpath)
|
@@ -126,15 +138,8 @@ module Rodauth
|
|
126
138
|
end
|
127
139
|
|
128
140
|
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)
|
141
|
+
warn("Deprecated #_login method called, use #login instead.")
|
142
|
+
login(auth_type)
|
138
143
|
end
|
139
144
|
end
|
140
145
|
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
|
@@ -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
|
data/lib/rodauth/migrations.rb
CHANGED
@@ -9,9 +9,14 @@ module Rodauth
|
|
9
9
|
case db.database_type
|
10
10
|
when :postgres
|
11
11
|
search_path = opts[:search_path] || 'public, pg_temp'
|
12
|
+
primary_key_type =
|
13
|
+
case db.schema(table_name).find { |row| row.first == :id }[1][:db_type]
|
14
|
+
when 'uuid' then :uuid
|
15
|
+
else :int8
|
16
|
+
end
|
12
17
|
|
13
18
|
db.run <<END
|
14
|
-
CREATE OR REPLACE FUNCTION #{get_salt_name}(acct_id
|
19
|
+
CREATE OR REPLACE FUNCTION #{get_salt_name}(acct_id #{primary_key_type}) RETURNS text AS $$
|
15
20
|
DECLARE salt text;
|
16
21
|
BEGIN
|
17
22
|
SELECT substr(password_hash, 0, 30) INTO salt
|
@@ -25,7 +30,7 @@ SET search_path = #{search_path};
|
|
25
30
|
END
|
26
31
|
|
27
32
|
db.run <<END
|
28
|
-
CREATE OR REPLACE FUNCTION #{valid_hash_name}(acct_id
|
33
|
+
CREATE OR REPLACE FUNCTION #{valid_hash_name}(acct_id #{primary_key_type}, hash text) RETURNS boolean AS $$
|
29
34
|
DECLARE valid boolean;
|
30
35
|
BEGIN
|
31
36
|
SELECT password_hash = hash INTO valid
|
@@ -100,13 +105,19 @@ END
|
|
100
105
|
end
|
101
106
|
|
102
107
|
def self.drop_database_authentication_functions(db, opts={})
|
108
|
+
table_name = opts[:table_name] || :account_password_hashes
|
103
109
|
get_salt_name = opts[:get_salt_name] || :rodauth_get_salt
|
104
110
|
valid_hash_name = opts[:valid_hash_name] || :rodauth_valid_password_hash
|
105
111
|
|
106
112
|
case db.database_type
|
107
113
|
when :postgres
|
108
|
-
|
109
|
-
|
114
|
+
primary_key_type =
|
115
|
+
case db.schema(table_name).find { |row| row.first == :id }[1][:db_type]
|
116
|
+
when 'uuid' then :uuid
|
117
|
+
else :int8
|
118
|
+
end
|
119
|
+
db.run "DROP FUNCTION #{get_salt_name}(#{primary_key_type})"
|
120
|
+
db.run "DROP FUNCTION #{valid_hash_name}(#{primary_key_type}, text)"
|
110
121
|
when :mysql, :mssql
|
111
122
|
db.run "DROP FUNCTION #{get_salt_name}"
|
112
123
|
db.run "DROP FUNCTION #{valid_hash_name}"
|
@@ -118,6 +129,6 @@ END
|
|
118
129
|
end
|
119
130
|
|
120
131
|
def self.drop_database_previous_password_check_functions(db, opts={})
|
121
|
-
drop_database_authentication_functions(db, {:get_salt_name=>:rodauth_get_previous_salt, :valid_hash_name=>:rodauth_previous_password_hash_match}.merge(opts))
|
132
|
+
drop_database_authentication_functions(db, {:table_name=>:account_previous_password_hashes, :get_salt_name=>:rodauth_get_previous_salt, :valid_hash_name=>:rodauth_previous_password_hash_match}.merge(opts))
|
122
133
|
end
|
123
134
|
end
|
data/lib/rodauth/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rodauth
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jeremy Evans
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-
|
11
|
+
date: 2020-08-21 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: sequel
|
@@ -304,6 +304,7 @@ extra_rdoc_files:
|
|
304
304
|
- doc/release_notes/2.0.0.txt
|
305
305
|
- doc/release_notes/2.1.0.txt
|
306
306
|
- doc/release_notes/2.2.0.txt
|
307
|
+
- doc/release_notes/2.3.0.txt
|
307
308
|
files:
|
308
309
|
- CHANGELOG
|
309
310
|
- MIT-LICENSE
|
@@ -384,6 +385,7 @@ files:
|
|
384
385
|
- doc/release_notes/2.0.0.txt
|
385
386
|
- doc/release_notes/2.1.0.txt
|
386
387
|
- doc/release_notes/2.2.0.txt
|
388
|
+
- doc/release_notes/2.3.0.txt
|
387
389
|
- doc/remember.rdoc
|
388
390
|
- doc/reset_password.rdoc
|
389
391
|
- doc/session_expiration.rdoc
|