rodauth 1.10.0 → 1.11.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 +8 -0
- data/doc/base.rdoc +4 -1
- data/doc/internals.rdoc +1 -1
- data/doc/jwt.rdoc +5 -0
- data/doc/password_expiration.rdoc +1 -1
- data/doc/release_notes/1.11.0.txt +32 -0
- data/lib/rodauth/features/base.rb +8 -6
- data/lib/rodauth/features/jwt.rb +39 -24
- data/lib/rodauth/features/password_expiration.rb +1 -1
- data/lib/rodauth/version.rb +1 -1
- data/spec/http_basic_auth_spec.rb +1 -1
- data/spec/jwt_spec.rb +23 -2
- data/spec/login_spec.rb +9 -0
- data/spec/password_expiration_spec.rb +3 -2
- metadata +5 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f15b58f396d567f884cc2aaf40794e07110e2091
|
4
|
+
data.tar.gz: 69a0eadaf802e8c82793c9c43084b0e4f442c8f7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7bcc3c3455093d1a1a21c358f99c4440a1a09ead7d34087a541490ab4e73acf1b7bea97ce0bbc2675392fbbae96b70ed9099ab63182355a647830d1877a143b5
|
7
|
+
data.tar.gz: 03b37be1351549b1e8b5c6a8e3c102a26c946501b11282081ce92213c9ab169077edf1a75b920589a3a4d1d905732d6cb6ac91197f181305f9002791ca109f35
|
data/CHANGELOG
CHANGED
@@ -1,3 +1,11 @@
|
|
1
|
+
=== 1.11.0 (2017-04-24)
|
2
|
+
|
3
|
+
* Add login_required_error_status, and use it in the jwt feature when custom error statuses are allowed (jeremyevans)
|
4
|
+
|
5
|
+
* Deal better with time differences between the database and application servers in the password_expiration plugin (jeremyevans)
|
6
|
+
|
7
|
+
* Add rodauth.valid_jwt? method for checking if a valid JWT was submitted with the request (jeremyevans)
|
8
|
+
|
1
9
|
=== 1.10.0 (2017-03-23)
|
2
10
|
|
3
11
|
* Add Internals Guide (jeremyevans)
|
data/doc/base.rdoc
CHANGED
@@ -16,7 +16,8 @@ account_password_hash_column :: Set if the password hash column is in the same
|
|
16
16
|
db :: The Sequel::Database object used for database access.
|
17
17
|
prefix :: The routing prefix used for Rodauth routes. If you are calling
|
18
18
|
in a routing subtree, this should be set to the root path of the
|
19
|
-
subtree.
|
19
|
+
subtree. This should include a leading slash if set, but not a
|
20
|
+
trailing slash.
|
20
21
|
require_bcrypt? :: Set to false to not require bcrypt, useful if using custom
|
21
22
|
authentication.
|
22
23
|
session_key :: The key in the session hash storing the primary key of the
|
@@ -50,6 +51,8 @@ lockout_error_status :: The response status to use a login is attempted to an ac
|
|
50
51
|
login_column :: The login column in the account model.
|
51
52
|
login_label :: The label to use for logins.
|
52
53
|
login_param :: The parameter name to use for logins.
|
54
|
+
login_required_error_status :: The response status to return when a login is required
|
55
|
+
and you are not logged in, if not redirecting, 401 by detault
|
53
56
|
modifications_require_password? :: Whether making changes to an account requires
|
54
57
|
the user reinputing their password.
|
55
58
|
no_matching_login_error_status :: The response status to use when the login is not
|
data/doc/internals.rdoc
CHANGED
@@ -149,7 +149,7 @@ Here's a heavily commented example showing what is going on inside a Rodauth fea
|
|
149
149
|
# methods are actually defined in terms of this method.
|
150
150
|
#
|
151
151
|
# So this particular method defines a foo_error_status method that will return 401 by
|
152
|
-
# default, but also adds a
|
152
|
+
# default, but also adds a configuration method that allows you to override the default.
|
153
153
|
auth_value_method :foo_error_status, 401
|
154
154
|
|
155
155
|
# This is similar to auth_value_method, but it only adds the configuration method.
|
data/doc/jwt.rdoc
CHANGED
@@ -33,6 +33,11 @@ will probably result in an error if a request to a Rodauth endpoint comes
|
|
33
33
|
in with a Content-Type that isn't application/json, unless you also set
|
34
34
|
<tt>only_json? false</tt> in your rodauth configuration.
|
35
35
|
|
36
|
+
If you would like to check if a valid JWT was submitted with the current
|
37
|
+
request in your Roda app, you can call the rodauth.valid_jwt? method. If
|
38
|
+
rodauth.valid_jwt? returns true, the contents of the jwt can be retrieved
|
39
|
+
from rodauth.session.
|
40
|
+
|
36
41
|
== Auth Value Methods
|
37
42
|
|
38
43
|
invalid_jwt_format_error_message :: The error message to use when a JWT with an invalid format is submitted in the Authorization header.
|
@@ -21,7 +21,7 @@ expiration is in general a net loss from a security perspective.
|
|
21
21
|
== Auth Value Methods
|
22
22
|
|
23
23
|
allow_password_change_after :: How long in seconds after the last password change
|
24
|
-
until another password change is allowed (
|
24
|
+
until another password change is allowed (always allowed by default).
|
25
25
|
password_expiration_error_flash :: The flash error to display when the account's
|
26
26
|
password has expired and needs to be changed.
|
27
27
|
password_not_changeable_yet_error_flash :: The flash error to display when not
|
@@ -0,0 +1,32 @@
|
|
1
|
+
= New Features
|
2
|
+
|
3
|
+
* A rodauth.valid_jwt? method has been added, allowing for easy
|
4
|
+
checking of whether a valid JWT has been submitted. If a valid
|
5
|
+
JWT has been submitted, the contents of the JWT will be available
|
6
|
+
in rodauth.session.
|
7
|
+
|
8
|
+
* If using the jwt feature with json_response_custom_error_status?
|
9
|
+
set to true, and going to a page that requires a login when not
|
10
|
+
logged in, a 401 error status will now be used instead of a 400
|
11
|
+
error status. You can customize this status using the new
|
12
|
+
login_required_error_status configuration method.
|
13
|
+
|
14
|
+
= Improvements
|
15
|
+
|
16
|
+
* Time differences between the database server and the application
|
17
|
+
server are now handled slightly better in the password_expiration
|
18
|
+
feature. This mostly affects testing, where sometimes tests would
|
19
|
+
previously fail if the database server time was ahead of the
|
20
|
+
application server time when testing whether a password change was
|
21
|
+
allowed.
|
22
|
+
|
23
|
+
* Some methods that were private by default, but public if overridden,
|
24
|
+
are now public by default. These include update_session and
|
25
|
+
only_json? in the base feature, and json_request?, jwt_secret, and
|
26
|
+
use_jwt? in the jwt feature.
|
27
|
+
|
28
|
+
= Backwards Compatibility
|
29
|
+
|
30
|
+
* The private jwt_payload method in the jwt feature now returns false
|
31
|
+
instead of redirecting on error. This should not affect the
|
32
|
+
application unless the method was being called explicitly.
|
@@ -23,6 +23,7 @@ module Rodauth
|
|
23
23
|
auth_value_method :invalid_password_error_status, 401
|
24
24
|
auth_value_method :invalid_password_message, "invalid password"
|
25
25
|
auth_value_method :login_column, :email
|
26
|
+
auth_value_method :login_required_error_status, 401
|
26
27
|
auth_value_method :lockout_error_status, 403
|
27
28
|
auth_value_method :password_hash_id_column, :id
|
28
29
|
auth_value_method :password_hash_column, :password_hash
|
@@ -182,6 +183,7 @@ module Rodauth
|
|
182
183
|
end
|
183
184
|
|
184
185
|
def login_required
|
186
|
+
set_redirect_error_status(login_required_error_status)
|
185
187
|
set_redirect_error_flash require_login_error_flash
|
186
188
|
redirect require_login_redirect
|
187
189
|
end
|
@@ -253,6 +255,10 @@ module Rodauth
|
|
253
255
|
_view(:render, page)
|
254
256
|
end
|
255
257
|
|
258
|
+
def only_json?
|
259
|
+
scope.class.opts[:rodauth_json] == :only
|
260
|
+
end
|
261
|
+
|
256
262
|
def post_configure
|
257
263
|
require 'bcrypt' if require_bcrypt?
|
258
264
|
db.extension :date_arithmetic if use_date_arithmetic?
|
@@ -273,13 +279,13 @@ module Rodauth
|
|
273
279
|
end
|
274
280
|
end
|
275
281
|
|
276
|
-
private
|
277
|
-
|
278
282
|
def update_session
|
279
283
|
clear_session
|
280
284
|
session[session_key] = account_session_value
|
281
285
|
end
|
282
286
|
|
287
|
+
private
|
288
|
+
|
283
289
|
# Return a string for the parameter name. This will be an empty
|
284
290
|
# string if the parameter doesn't exist.
|
285
291
|
def param(key)
|
@@ -412,10 +418,6 @@ module Rodauth
|
|
412
418
|
{account_status_column=>account_open_status_value}
|
413
419
|
end
|
414
420
|
|
415
|
-
def only_json?
|
416
|
-
scope.class.opts[:rodauth_json] == :only
|
417
|
-
end
|
418
|
-
|
419
421
|
def template_path(page)
|
420
422
|
File.join(File.dirname(__FILE__), '../../../templates', "#{page}.str")
|
421
423
|
end
|
data/lib/rodauth/features/jwt.rb
CHANGED
@@ -43,11 +43,25 @@ module Rodauth
|
|
43
43
|
return super unless use_jwt?
|
44
44
|
|
45
45
|
s = {}
|
46
|
-
if jwt_token
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
46
|
+
if jwt_token
|
47
|
+
unless session_data = jwt_payload
|
48
|
+
json_response[json_response_error_key] = invalid_jwt_format_error_message
|
49
|
+
response.status ||= json_response_error_status
|
50
|
+
response['Content-Type'] ||= json_response_content_type
|
51
|
+
response.write(request.send(:convert_to_json, json_response))
|
52
|
+
request.halt
|
53
|
+
end
|
54
|
+
|
55
|
+
if jwt_session_key
|
56
|
+
session_data = session_data[jwt_session_key]
|
57
|
+
end
|
58
|
+
|
59
|
+
if session_data
|
60
|
+
if jwt_symbolize_deeply?
|
61
|
+
s = JSON.parse(JSON.fast_generate(session_data), :symbolize_names=>true)
|
62
|
+
else
|
63
|
+
session_data.each{|k,v| s[k.to_sym] = v}
|
64
|
+
end
|
51
65
|
end
|
52
66
|
end
|
53
67
|
@session = s
|
@@ -83,6 +97,15 @@ module Rodauth
|
|
83
97
|
json_response[json_response_success_key] = message if include_success_messages?
|
84
98
|
end
|
85
99
|
|
100
|
+
def json_request?
|
101
|
+
return @json_request if defined?(@json_request)
|
102
|
+
@json_request = request.content_type =~ json_request_content_type_regexp
|
103
|
+
end
|
104
|
+
|
105
|
+
def jwt_secret
|
106
|
+
raise ArgumentError, "jwt_secret not set"
|
107
|
+
end
|
108
|
+
|
86
109
|
def jwt_session_hash
|
87
110
|
jwt_session_key ? {jwt_session_key=>session} : session
|
88
111
|
end
|
@@ -103,6 +126,14 @@ module Rodauth
|
|
103
126
|
response.headers['Authorization'] = token
|
104
127
|
end
|
105
128
|
|
129
|
+
def use_jwt?
|
130
|
+
jwt_token || only_json? || json_request?
|
131
|
+
end
|
132
|
+
|
133
|
+
def valid_jwt?
|
134
|
+
!!(jwt_token && jwt_payload)
|
135
|
+
end
|
136
|
+
|
106
137
|
private
|
107
138
|
|
108
139
|
def before_rodauth
|
@@ -139,17 +170,10 @@ module Rodauth
|
|
139
170
|
end
|
140
171
|
|
141
172
|
def jwt_payload
|
142
|
-
@jwt_payload
|
173
|
+
return @jwt_payload if defined?(@jwt_payload)
|
174
|
+
@jwt_payload = JWT.decode(jwt_token, jwt_secret, true, jwt_decode_opts.merge(:algorithm=>jwt_algorithm))[0]
|
143
175
|
rescue JWT::DecodeError
|
144
|
-
|
145
|
-
response.status ||= json_response_error_status
|
146
|
-
response['Content-Type'] ||= json_response_content_type
|
147
|
-
response.write(request.send(:convert_to_json, json_response))
|
148
|
-
request.halt
|
149
|
-
end
|
150
|
-
|
151
|
-
def jwt_secret
|
152
|
-
raise ArgumentError, "jwt_secret not set"
|
176
|
+
@jwt_payload = false
|
153
177
|
end
|
154
178
|
|
155
179
|
def redirect(_)
|
@@ -189,11 +213,6 @@ module Rodauth
|
|
189
213
|
set_jwt_token(session_jwt)
|
190
214
|
end
|
191
215
|
|
192
|
-
def json_request?
|
193
|
-
return @json_request if defined?(@json_request)
|
194
|
-
@json_request = request.content_type =~ json_request_content_type_regexp
|
195
|
-
end
|
196
|
-
|
197
216
|
def set_redirect_error_status(status)
|
198
217
|
if json_request? && json_response_custom_error_status?
|
199
218
|
response.status = status
|
@@ -207,9 +226,5 @@ module Rodauth
|
|
207
226
|
|
208
227
|
super
|
209
228
|
end
|
210
|
-
|
211
|
-
def use_jwt?
|
212
|
-
jwt_token || only_json? || json_request?
|
213
|
-
end
|
214
229
|
end
|
215
230
|
end
|
@@ -10,7 +10,7 @@ module Rodauth
|
|
10
10
|
redirect :password_not_changeable_yet
|
11
11
|
redirect(:password_change_needed){"#{prefix}/#{change_password_route}"}
|
12
12
|
|
13
|
-
auth_value_method :allow_password_change_after,
|
13
|
+
auth_value_method :allow_password_change_after, -86400
|
14
14
|
auth_value_method :require_password_change_after, 90*86400
|
15
15
|
auth_value_method :password_expiration_table, :account_password_change_times
|
16
16
|
auth_value_method :password_expiration_id_column, :id
|
data/lib/rodauth/version.rb
CHANGED
@@ -104,7 +104,7 @@ describe "Rodauth http basic auth feature" do
|
|
104
104
|
|
105
105
|
@authorization = nil
|
106
106
|
res = basic_auth_json_request(:auth=>'.')
|
107
|
-
res.must_equal [
|
107
|
+
res.must_equal [401, {'error'=>"Please login to continue"}]
|
108
108
|
|
109
109
|
@authorization = nil
|
110
110
|
res = basic_auth_json_request(:username=>'foo@example2.com')
|
data/spec/jwt_spec.rb
CHANGED
@@ -11,10 +11,10 @@ describe 'Rodauth login feature' do
|
|
11
11
|
end
|
12
12
|
|
13
13
|
res = json_request("/", :headers=>{'HTTP_AUTHORIZATION'=>'Basic foo'})
|
14
|
-
res.must_equal [
|
14
|
+
res.must_equal [401, {'error'=>'Please login to continue'}]
|
15
15
|
|
16
16
|
res = json_request("/", :headers=>{'HTTP_AUTHORIZATION'=>'Digest foo'})
|
17
|
-
res.must_equal [
|
17
|
+
res.must_equal [401, {'error'=>'Please login to continue'}]
|
18
18
|
end
|
19
19
|
|
20
20
|
it "should return error message if invalid JWT format used in request Authorization header" do
|
@@ -103,6 +103,27 @@ describe 'Rodauth login feature' do
|
|
103
103
|
res.must_equal [405, {'error'=>'non-POST method used in JSON API'}]
|
104
104
|
end
|
105
105
|
|
106
|
+
it "should support valid_jwt? method for checking for valid JWT tokens" do
|
107
|
+
rodauth do
|
108
|
+
enable :login, :logout, :jwt
|
109
|
+
jwt_secret '1'
|
110
|
+
json_response_success_key 'success'
|
111
|
+
end
|
112
|
+
roda(:jwt) do |r|
|
113
|
+
r.rodauth
|
114
|
+
[rodauth.valid_jwt?.to_s]
|
115
|
+
end
|
116
|
+
|
117
|
+
res = json_request("/", :method=>'GET')
|
118
|
+
res.must_equal [200, ['false']]
|
119
|
+
|
120
|
+
res = json_request("/login", :method=>'GET')
|
121
|
+
res.must_equal [405, {'error'=>'non-POST method used in JSON API'}]
|
122
|
+
|
123
|
+
res = json_request("/", :method=>'GET')
|
124
|
+
res.must_equal [200, ['true']]
|
125
|
+
end
|
126
|
+
|
106
127
|
it "should require Accept contain application/json if jwt_check_accept? is true and Accept is present" do
|
107
128
|
rodauth do
|
108
129
|
enable :login, :logout, :jwt
|
data/spec/login_spec.rb
CHANGED
@@ -188,11 +188,18 @@ describe 'Rodauth login feature' do
|
|
188
188
|
roda(:jwt) do |r|
|
189
189
|
r.rodauth
|
190
190
|
response['Content-Type'] = 'application/json'
|
191
|
+
r.post('foo') do
|
192
|
+
rodauth.require_login
|
193
|
+
'3'
|
194
|
+
end
|
191
195
|
rodauth.logged_in? ? '1' : '2'
|
192
196
|
end
|
193
197
|
|
194
198
|
json_request.must_equal [200, 2]
|
195
199
|
|
200
|
+
res = json_request("/foo")
|
201
|
+
res.must_equal [401, {"error"=>"Please login to continue"}]
|
202
|
+
|
196
203
|
res = json_request("/login", :login=>'foo@example2.com', :password=>'0123456789')
|
197
204
|
res.must_equal [401, {'error'=>"There was an error logging in", "field-error"=>["login", "no matching login"]}]
|
198
205
|
|
@@ -202,6 +209,8 @@ describe 'Rodauth login feature' do
|
|
202
209
|
json_request("/login", :login=>'foo@example.com', :password=>'0123456789').must_equal [200, {"success"=>'You have been logged in'}]
|
203
210
|
json_request.must_equal [200, 1]
|
204
211
|
|
212
|
+
res = json_request("/foo").must_equal [200, 3]
|
213
|
+
|
205
214
|
json_request("/logout").must_equal [200, {"success"=>'You have been logged out'}]
|
206
215
|
json_request.must_equal [200, 2]
|
207
216
|
end
|
@@ -152,8 +152,9 @@ describe 'Rodauth password expiration feature' do
|
|
152
152
|
rodauth do
|
153
153
|
enable :login, :change_password, :password_expiration
|
154
154
|
password_expiration_default true
|
155
|
+
allow_password_change_after -1000
|
155
156
|
change_password_requires_password? false
|
156
|
-
require_password_change_after
|
157
|
+
require_password_change_after 3600
|
157
158
|
end
|
158
159
|
roda do |r|
|
159
160
|
r.rodauth
|
@@ -175,7 +176,7 @@ describe 'Rodauth password expiration feature' do
|
|
175
176
|
visit "/expire/90"
|
176
177
|
page.current_path.must_equal '/'
|
177
178
|
|
178
|
-
visit "/expire/
|
179
|
+
visit "/expire/7200"
|
179
180
|
page.current_path.must_equal '/change-password'
|
180
181
|
end
|
181
182
|
|
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: 1.
|
4
|
+
version: 1.11.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: 2017-
|
11
|
+
date: 2017-04-24 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: sequel
|
@@ -239,6 +239,7 @@ extra_rdoc_files:
|
|
239
239
|
- doc/release_notes/1.8.0.txt
|
240
240
|
- doc/release_notes/1.9.0.txt
|
241
241
|
- doc/release_notes/1.10.0.txt
|
242
|
+
- doc/release_notes/1.11.0.txt
|
242
243
|
files:
|
243
244
|
- CHANGELOG
|
244
245
|
- MIT-LICENSE
|
@@ -268,6 +269,7 @@ files:
|
|
268
269
|
- doc/release_notes/1.0.0.txt
|
269
270
|
- doc/release_notes/1.1.0.txt
|
270
271
|
- doc/release_notes/1.10.0.txt
|
272
|
+
- doc/release_notes/1.11.0.txt
|
271
273
|
- doc/release_notes/1.2.0.txt
|
272
274
|
- doc/release_notes/1.3.0.txt
|
273
275
|
- doc/release_notes/1.4.0.txt
|
@@ -420,7 +422,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
420
422
|
version: '0'
|
421
423
|
requirements: []
|
422
424
|
rubyforge_project:
|
423
|
-
rubygems_version: 2.6.
|
425
|
+
rubygems_version: 2.6.11
|
424
426
|
signing_key:
|
425
427
|
specification_version: 4
|
426
428
|
summary: Authentication and Account Management Framework for Rack Applications
|