rodauth 1.10.0 → 1.11.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|