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 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