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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: b1741b1db3b830dd1edde47d251c388e51901d85
4
- data.tar.gz: 1066a17b7b2a3565335cbcd47ef6903d3c1f5a93
3
+ metadata.gz: f15b58f396d567f884cc2aaf40794e07110e2091
4
+ data.tar.gz: 69a0eadaf802e8c82793c9c43084b0e4f442c8f7
5
5
  SHA512:
6
- metadata.gz: 76b1d26463432bb939565f09b3779cbdf6f8d7b632079115eaf62288c2817f80c1f24752e3847893e17242eadb21eccfae5cc7d7df54a45f4f7be2032207e751
7
- data.tar.gz: 59c18ab583fde50543c9284a7831d2fbf2087d64bb8e5c33a257b82cd3b3ded7c6d28f1a38a6a32cbba04d1c34ff9d2d87264a9356fc44cc40c02ceac8298454
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 cofniguration method that allows you to override the default.
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 (0 by default).
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
@@ -43,11 +43,25 @@ module Rodauth
43
43
  return super unless use_jwt?
44
44
 
45
45
  s = {}
46
- if jwt_token && (session_data = jwt_session_key ? jwt_payload[jwt_session_key] : jwt_payload)
47
- if jwt_symbolize_deeply?
48
- s = JSON.parse(JSON.fast_generate(session_data), :symbolize_names=>true)
49
- else
50
- session_data.each{|k,v| s[k.to_sym] = v}
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 ||= JWT.decode(jwt_token, jwt_secret, true, jwt_decode_opts.merge(:algorithm=>jwt_algorithm))[0]
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
- json_response[json_response_error_key] = invalid_jwt_format_error_message
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, 0
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
@@ -1,7 +1,7 @@
1
1
  # frozen-string-literal: true
2
2
 
3
3
  module Rodauth
4
- VERSION = '1.10.0'.freeze
4
+ VERSION = '1.11.0'.freeze
5
5
 
6
6
  def self.version
7
7
  VERSION
@@ -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 [400, {'error'=>"Please login to continue"}]
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 [400, {'error'=>'Please login to continue'}]
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 [400, {'error'=>'Please login to continue'}]
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 100
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/110"
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.10.0
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-03-23 00:00:00.000000000 Z
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.8
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