rodauth 2.11.0 → 2.15.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +28 -0
  3. data/README.rdoc +29 -7
  4. data/doc/active_sessions.rdoc +4 -0
  5. data/doc/base.rdoc +1 -0
  6. data/doc/error_reasons.rdoc +73 -0
  7. data/doc/internal_request.rdoc +463 -0
  8. data/doc/path_class_methods.rdoc +10 -0
  9. data/doc/release_notes/2.11.0.txt +1 -1
  10. data/doc/release_notes/2.12.0.txt +17 -0
  11. data/doc/release_notes/2.13.0.txt +19 -0
  12. data/doc/release_notes/2.14.0.txt +17 -0
  13. data/doc/release_notes/2.15.0.txt +48 -0
  14. data/doc/remember.rdoc +1 -0
  15. data/lib/rodauth.rb +9 -2
  16. data/lib/rodauth/features/active_sessions.rb +29 -8
  17. data/lib/rodauth/features/base.rb +26 -1
  18. data/lib/rodauth/features/change_login.rb +6 -4
  19. data/lib/rodauth/features/change_password.rb +5 -3
  20. data/lib/rodauth/features/close_account.rb +3 -1
  21. data/lib/rodauth/features/confirm_password.rb +2 -2
  22. data/lib/rodauth/features/create_account.rb +6 -4
  23. data/lib/rodauth/features/disallow_common_passwords.rb +1 -1
  24. data/lib/rodauth/features/disallow_password_reuse.rb +1 -1
  25. data/lib/rodauth/features/email_auth.rb +6 -0
  26. data/lib/rodauth/features/internal_request.rb +367 -0
  27. data/lib/rodauth/features/jwt_refresh.rb +1 -1
  28. data/lib/rodauth/features/lockout.rb +15 -4
  29. data/lib/rodauth/features/login.rb +6 -3
  30. data/lib/rodauth/features/login_password_requirements_base.rb +15 -6
  31. data/lib/rodauth/features/otp.rb +13 -6
  32. data/lib/rodauth/features/password_complexity.rb +4 -4
  33. data/lib/rodauth/features/path_class_methods.rb +22 -0
  34. data/lib/rodauth/features/recovery_codes.rb +6 -2
  35. data/lib/rodauth/features/remember.rb +25 -10
  36. data/lib/rodauth/features/reset_password.rb +8 -4
  37. data/lib/rodauth/features/session_expiration.rb +1 -0
  38. data/lib/rodauth/features/single_session.rb +1 -0
  39. data/lib/rodauth/features/sms_codes.rb +17 -5
  40. data/lib/rodauth/features/two_factor_base.rb +6 -1
  41. data/lib/rodauth/features/verify_account.rb +8 -1
  42. data/lib/rodauth/features/verify_account_grace_period.rb +1 -1
  43. data/lib/rodauth/features/verify_login_change.rb +5 -2
  44. data/lib/rodauth/features/webauthn.rb +15 -14
  45. data/lib/rodauth/features/webauthn_login.rb +1 -1
  46. data/lib/rodauth/version.rb +1 -1
  47. data/templates/button.str +1 -1
  48. data/templates/change-password.str +2 -2
  49. data/templates/global-logout-field.str +1 -1
  50. data/templates/login-confirm-field.str +2 -2
  51. data/templates/login-display.str +2 -2
  52. data/templates/login-field.str +2 -2
  53. data/templates/otp-auth-code-field.str +2 -2
  54. data/templates/otp-setup.str +2 -2
  55. data/templates/password-confirm-field.str +2 -2
  56. data/templates/password-field.str +2 -2
  57. data/templates/recovery-auth.str +2 -2
  58. data/templates/remember.str +1 -1
  59. data/templates/sms-code-field.str +2 -2
  60. data/templates/sms-setup.str +2 -2
  61. data/templates/unlock-account-email.str +1 -1
  62. data/templates/webauthn-remove.str +1 -1
  63. metadata +19 -3
@@ -0,0 +1,10 @@
1
+ = Documentation for Path Class Methods Feature
2
+
3
+ The path class methods feature allows for calling the *_path and *_url
4
+ methods directly on the class, as opposed to an instance of the class.
5
+
6
+ In order for the *_url methods to be used, you must use the base_url
7
+ configuration so that determining the base URL doesn't depend on the
8
+ submitted request, as the request will not be set when using the
9
+ class method. Failure to do this will probably result in a NoMethodError
10
+ being raised.
@@ -23,7 +23,7 @@
23
23
  block. Previously, you could only call configuration methods in
24
24
  the block that added the feature, and enabling a feature in a
25
25
  block that was already enabled in a previous block did not allow
26
- the use of configuraton methods related to the feature.
26
+ the use of configuration methods related to the feature.
27
27
 
28
28
  * Passing a block when loading the rodauth plugin is now optional.
29
29
 
@@ -0,0 +1,17 @@
1
+ = New Features
2
+
3
+ * The following configuration methods have been added to the
4
+ active_sessions feature:
5
+
6
+ * active_sessions_insert_hash
7
+ * active_sessions_key
8
+ * active_sessions_update_hash
9
+ * update_current_session?
10
+
11
+ These methods allow you to control what gets inserted and
12
+ updated into the active_sessions_table, and to control
13
+ whether to perform updates.
14
+
15
+ = Other Improvements
16
+
17
+ * A typo was fixed in the default unlock account email.
@@ -0,0 +1,19 @@
1
+ = New Features
2
+
3
+ * A set_error_reason configuration method has been added. This method
4
+ is called whenever a error occurs in Rodauth, with a symbol
5
+ describing the error. The default implementation of this method does
6
+ nothing, it has been added to make it easier for Rodauth users to
7
+ implement custom handling for specific error types. See the Rodauth
8
+ documentation for this method to see the list of symbols this method
9
+ can be called with.
10
+
11
+ = Other Improvements
12
+
13
+ * When using active_sessions and jwt_refresh together, and allowing for
14
+ expired JWTs when refreshing, you can now call
15
+ rodauth.check_active_session before r.rodauth. Previously, this
16
+ did not work, and you had to call rodauth.check_active_session
17
+ after r.rodauth.
18
+
19
+ * The default templates now also support Bootstrap 5.
@@ -0,0 +1,17 @@
1
+ = New Features
2
+
3
+ * A remembered_session_id method has been added for getting the
4
+ account id from a valid remember token, without modifying the
5
+ session to log the account in.
6
+
7
+ = Other Improvements
8
+
9
+ * The jwt_refresh feature's support for allowing refresh with
10
+ an expired access token now works even if the Rodauth
11
+ configuration uses an incorrect prefix.
12
+
13
+ * The internal account_in_unverified_grace_period? method now
14
+ returns false if an account has not been loaded and the
15
+ session has not been logged in. Previously, calling this
16
+ method in such cases would result in an exception being
17
+ raised.
@@ -0,0 +1,48 @@
1
+ = New Features
2
+
3
+ * An internal_request feature has been added. This feature allows
4
+ for interacting with Rodauth by calling methods, instead of having
5
+ to use a website or JSON API. This feature is designed primarily
6
+ for administrative use, so that administrators can create accounts,
7
+ change passwords or logins for accounts, and handle similar actions
8
+ without the user of the account being involved.
9
+
10
+ For example, assuming you've loaded the change_password and
11
+ internal_request features, and that your Roda class that
12
+ is loading Rodauth is named App, you can change the password
13
+ for the account with id 1 using:
14
+
15
+ App.rodauth.change_password(account_id: 1, password: 'foobar')
16
+
17
+ The internal request methods are implemented as class methods
18
+ on the Rodauth::Auth subclass (the object returned by App.rodauth).
19
+ These methods call methods on a subclass of that class specific
20
+ to internal requests.
21
+
22
+ The reason the feature is named internal_request is that these
23
+ methods are implemented by submitting a request internally, that is
24
+ processed almost exactly the same way as Rodauth would process a
25
+ web request.
26
+
27
+ See the internal_request feature documentation for details on which
28
+ internal request methods are available and the options they take.
29
+
30
+ * A path_class_methods feature has been added, that allows for calling
31
+ *_path and *_url as class methods. If you would like to call the
32
+ *_url methods as class methods, make sure to use the base_url
33
+ configuration method to set the base URL so that it does not require
34
+ request-specific information.
35
+
36
+ * Rodauth::Auth classes now have a configuration_name method that
37
+ returns the configuration name associated with the class. They also
38
+ have a configuration method that returns the configuration
39
+ associated with the class.
40
+
41
+ * Rodauth::Feature now supports an internal_request_method method for
42
+ specifying which methods are supported as internal request methods.
43
+
44
+ = Other Improvements
45
+
46
+ * The default base_url configuration method will now use the domain
47
+ method to get the domain to use, instead of getting the domain
48
+ information directly from the request environment.
data/doc/remember.rdoc CHANGED
@@ -69,6 +69,7 @@ generate_remember_key_value :: A random string to use as the remember key.
69
69
  get_remember_key :: Retrieve the remember key from the database.
70
70
  load_memory :: If the remember key cookie is included in the request, and the user is not currently logged in, check the remember keys table and autologin the user if the remember key cookie matches the current remember key for the account. This method needs to be called manually inside the Roda route block to autologin users.
71
71
  logged_in_via_remember_key? :: Whether the current session was logged in via a remember key.
72
+ remembered_session_id :: The session_id which is validly remembered, if any.
72
73
  remember_key_value :: The current value of the remember key/token.
73
74
  remember_login :: Set the cookie containing the remember token, so that future sessions will be autologged in.
74
75
  remember_view :: The HTML to use for the change remember settings form.
data/lib/rodauth.rb CHANGED
@@ -39,11 +39,11 @@ module Rodauth
39
39
  else
40
40
  json_opt != :only
41
41
  end
42
- auth_class = (app.opts[:rodauths] ||= {})[opts[:name]] ||= opts[:auth_class] || Class.new(Auth)
42
+ auth_class = (app.opts[:rodauths] ||= {})[opts[:name]] ||= opts[:auth_class] || Class.new(Auth){@configuration_name = opts[:name]}
43
43
  if !auth_class.roda_class
44
44
  auth_class.roda_class = app
45
45
  elsif auth_class.roda_class != app
46
- auth_class = app.opts[:rodauths][opts[:name]] = Class.new(auth_class)
46
+ auth_class = app.opts[:rodauths][opts[:name]] = Class.new(auth_class){@configuration_name = opts[:name]}
47
47
  auth_class.roda_class = app
48
48
  end
49
49
  auth_class.configure(&block) if block
@@ -107,6 +107,7 @@ module Rodauth
107
107
  attr_accessor :dependencies
108
108
  attr_accessor :routes
109
109
  attr_accessor :configuration
110
+ attr_reader :internal_request_methods
110
111
 
111
112
  def route(name=feature_name, default=name.to_s.tr('_', '-'), &block)
112
113
  route_meth = :"#{name}_route"
@@ -152,6 +153,10 @@ module Rodauth
152
153
  FEATURES[name] = feature
153
154
  end
154
155
 
156
+ def internal_request_method(name=feature_name)
157
+ (@internal_request_methods ||= []) << name
158
+ end
159
+
155
160
  def configuration_module_eval(&block)
156
161
  configuration.module_eval(&block)
157
162
  end
@@ -260,6 +265,8 @@ module Rodauth
260
265
  attr_reader :features
261
266
  attr_reader :routes
262
267
  attr_accessor :route_hash
268
+ attr_reader :configuration_name
269
+ attr_reader :configuration
263
270
  end
264
271
 
265
272
  def self.inherited(subclass)
@@ -19,7 +19,12 @@ module Rodauth
19
19
  auth_value_method :session_inactivity_deadline, 86400
20
20
  auth_value_method(:session_lifetime_deadline, 86400*30)
21
21
 
22
+ auth_value_methods :update_current_session?
23
+
22
24
  auth_methods(
25
+ :active_sessions_insert_hash,
26
+ :active_sessions_key,
27
+ :active_sessions_update_hash,
23
28
  :add_active_session,
24
29
  :currently_active_session?,
25
30
  :handle_duplicate_active_session_id,
@@ -36,8 +41,8 @@ module Rodauth
36
41
  ds = active_sessions_ds.
37
42
  where(active_sessions_session_id_column => compute_hmac(session_id))
38
43
 
39
- if session_inactivity_deadline
40
- ds.update(active_sessions_last_use_column => Sequel::CURRENT_TIMESTAMP) == 1
44
+ if update_current_session?
45
+ ds.update(active_sessions_update_hash) == 1
41
46
  else
42
47
  ds.count == 1
43
48
  end
@@ -52,16 +57,15 @@ module Rodauth
52
57
  def no_longer_active_session
53
58
  clear_session
54
59
  set_redirect_error_status inactive_session_error_status
60
+ set_error_reason :inactive_session
55
61
  set_redirect_error_flash active_sessions_error_flash
56
62
  redirect active_sessions_redirect
57
63
  end
58
64
 
59
65
  def add_active_session
60
- key = random_key
66
+ key = generate_active_sessions_key
61
67
  set_session_value(session_id_session_key, key)
62
- if e = raises_uniqueness_violation? do
63
- active_sessions_ds.insert(active_sessions_account_id_column => session_value, active_sessions_session_id_column => compute_hmac(key))
64
- end
68
+ if e = raises_uniqueness_violation?{active_sessions_ds.insert(active_sessions_insert_hash)}
65
69
  handle_duplicate_active_session_id(e)
66
70
  end
67
71
  nil
@@ -104,7 +108,7 @@ module Rodauth
104
108
  def after_refresh_token
105
109
  super if defined?(super)
106
110
  if prev_key = session[session_id_session_key]
107
- key = random_key
111
+ key = generate_active_sessions_key
108
112
  set_session_value(session_id_session_key, key)
109
113
  active_sessions_ds.
110
114
  where(active_sessions_session_id_column => compute_hmac(prev_key)).
@@ -126,6 +130,20 @@ module Rodauth
126
130
  super
127
131
  end
128
132
 
133
+ attr_reader :active_sessions_key
134
+
135
+ def generate_active_sessions_key
136
+ @active_sessions_key = random_key
137
+ end
138
+
139
+ def active_sessions_insert_hash
140
+ {active_sessions_account_id_column => session_value, active_sessions_session_id_column => compute_hmac(active_sessions_key)}
141
+ end
142
+
143
+ def active_sessions_update_hash
144
+ {active_sessions_last_use_column => Sequel::CURRENT_TIMESTAMP}
145
+ end
146
+
129
147
  def session_inactivity_deadline_condition
130
148
  if deadline = session_inactivity_deadline
131
149
  Sequel[active_sessions_last_use_column] < Sequel.date_sub(Sequel::CURRENT_TIMESTAMP, seconds: deadline)
@@ -145,6 +163,10 @@ module Rodauth
145
163
  Sequel.|(*[cond, cond2].compact)
146
164
  end
147
165
 
166
+ def update_current_session?
167
+ !!session_inactivity_deadline
168
+ end
169
+
148
170
  def active_sessions_ds
149
171
  db[active_sessions_table].
150
172
  where(active_sessions_account_id_column=>session_value)
@@ -155,4 +177,3 @@ module Rodauth
155
177
  end
156
178
  end
157
179
  end
158
-
@@ -100,6 +100,7 @@ module Rodauth
100
100
  :set_notice_flash,
101
101
  :set_notice_now_flash,
102
102
  :set_redirect_error_flash,
103
+ :set_error_reason,
103
104
  :set_title,
104
105
  :translate,
105
106
  :update_session
@@ -114,6 +115,10 @@ module Rodauth
114
115
  :around_rodauth
115
116
  )
116
117
 
118
+ internal_request_method :account_exists?
119
+ internal_request_method :account_id_for_login
120
+ internal_request_method :internal_request_eval
121
+
117
122
  configuration_module_eval do
118
123
  def auth_class_eval(&block)
119
124
  auth.class_eval(&block)
@@ -294,6 +299,7 @@ module Rodauth
294
299
 
295
300
  def login_required
296
301
  set_redirect_error_status(login_required_error_status)
302
+ set_error_reason :login_required
297
303
  set_redirect_error_flash require_login_error_flash
298
304
  redirect require_login_redirect
299
305
  end
@@ -443,7 +449,9 @@ module Rodauth
443
449
  end
444
450
 
445
451
  def base_url
446
- request.base_url
452
+ url = String.new("#{request.scheme}://#{domain}")
453
+ url << ":#{request.port}" if request.port != Rack::Request::DEFAULT_PORTS[request.scheme]
454
+ url
447
455
  end
448
456
 
449
457
  def domain
@@ -539,6 +547,11 @@ module Rodauth
539
547
  def set_response_error_status(status)
540
548
  response.status = status
541
549
  end
550
+
551
+ def set_response_error_reason_status(reason, status)
552
+ set_error_reason(reason)
553
+ set_response_error_status(status)
554
+ end
542
555
 
543
556
  def throw_error(field, error)
544
557
  set_field_error(field, error)
@@ -550,6 +563,14 @@ module Rodauth
550
563
  throw_error(field, error)
551
564
  end
552
565
 
566
+ def set_error_reason(reason)
567
+ end
568
+
569
+ def throw_error_reason(reason, status, field, message)
570
+ set_error_reason(reason)
571
+ throw_error_status(status, field, message)
572
+ end
573
+
553
574
  def use_date_arithmetic?
554
575
  set_deadline_values?
555
576
  end
@@ -719,6 +740,10 @@ module Rodauth
719
740
  end
720
741
  end
721
742
 
743
+ def internal_request?
744
+ false
745
+ end
746
+
722
747
  def set_session_value(key, value)
723
748
  session[key] = value
724
749
  end
@@ -19,6 +19,8 @@ module Rodauth
19
19
 
20
20
  auth_methods :change_login
21
21
 
22
+ internal_request_method
23
+
22
24
  route do |r|
23
25
  require_account
24
26
  before_change_login_route
@@ -30,7 +32,7 @@ module Rodauth
30
32
  r.post do
31
33
  catch_error do
32
34
  if change_login_requires_password? && !password_match?(param(password_param))
33
- throw_error_status(invalid_password_error_status, password_param, invalid_password_message)
35
+ throw_error_reason(:invalid_password, invalid_password_error_status, password_param, invalid_password_message)
34
36
  end
35
37
 
36
38
  login = param(login_param)
@@ -39,7 +41,7 @@ module Rodauth
39
41
  end
40
42
 
41
43
  if require_login_confirmation? && login != param(login_confirm_param)
42
- throw_error_status(unmatched_field_error_status, login_param, logins_do_not_match_message)
44
+ throw_error_reason(:logins_do_not_match, unmatched_field_error_status, login_param, logins_do_not_match_message)
43
45
  end
44
46
 
45
47
  transaction do
@@ -65,7 +67,7 @@ module Rodauth
65
67
 
66
68
  def change_login(login)
67
69
  if account_ds.get(login_column).downcase == login.downcase
68
- @login_requirement_message = same_as_current_login_message
70
+ set_login_requirement_error_message(:same_as_current_login, same_as_current_login_message)
69
71
  return false
70
72
  end
71
73
 
@@ -82,7 +84,7 @@ module Rodauth
82
84
  updated = nil
83
85
  raised = raises_uniqueness_violation?{updated = update_account({login_column=>login}, account_ds.exclude(login_column=>login)) == 1}
84
86
  if raised
85
- @login_requirement_message = already_an_account_with_this_login_message
87
+ set_login_requirement_error_message(:already_an_account_with_this_login, already_an_account_with_this_login_message)
86
88
  end
87
89
  updated && !raised
88
90
  end
@@ -22,6 +22,8 @@ module Rodauth
22
22
  :invalid_previous_password_message
23
23
  )
24
24
 
25
+ internal_request_method
26
+
25
27
  route do |r|
26
28
  require_account
27
29
  before_change_password_route
@@ -33,16 +35,16 @@ module Rodauth
33
35
  r.post do
34
36
  catch_error do
35
37
  if change_password_requires_password? && !password_match?(param(password_param))
36
- throw_error_status(invalid_password_error_status, password_param, invalid_previous_password_message)
38
+ throw_error_reason(:invalid_previous_password, invalid_password_error_status, password_param, invalid_previous_password_message)
37
39
  end
38
40
 
39
41
  password = param(new_password_param)
40
42
  if require_password_confirmation? && password != param(password_confirm_param)
41
- throw_error_status(unmatched_field_error_status, new_password_param, passwords_do_not_match_message)
43
+ throw_error_reason(:passwords_do_not_match, unmatched_field_error_status, new_password_param, passwords_do_not_match_message)
42
44
  end
43
45
 
44
46
  if password_match?(password)
45
- throw_error_status(invalid_field_error_status, new_password_param, same_as_existing_password_message)
47
+ throw_error_reason(:same_as_existing_password, invalid_field_error_status, new_password_param, same_as_existing_password_message)
46
48
  end
47
49
 
48
50
  unless password_meets_requirements?(password)
@@ -24,6 +24,8 @@ module Rodauth
24
24
  :delete_account
25
25
  )
26
26
 
27
+ internal_request_method
28
+
27
29
  route do |r|
28
30
  require_account
29
31
  before_close_account_route
@@ -35,7 +37,7 @@ module Rodauth
35
37
  r.post do
36
38
  catch_error do
37
39
  if close_account_requires_password? && !password_match?(param(password_param))
38
- throw_error_status(invalid_password_error_status, password_param, invalid_password_message)
40
+ throw_error_reason(:invalid_password, invalid_password_error_status, password_param, invalid_password_message)
39
41
  end
40
42
 
41
43
  transaction do
@@ -40,7 +40,7 @@ module Rodauth
40
40
  set_notice_flash confirm_password_notice_flash
41
41
  redirect confirm_password_redirect
42
42
  else
43
- set_response_error_status(invalid_password_error_status)
43
+ set_response_error_reason_status(:invalid_password, invalid_password_error_status)
44
44
  set_field_error(password_param, invalid_password_message)
45
45
  set_error_flash confirm_password_error_flash
46
46
  confirm_password_view
@@ -53,6 +53,7 @@ module Rodauth
53
53
 
54
54
  if require_password_authentication? && has_password?
55
55
  set_redirect_error_status(password_authentication_required_error_status)
56
+ set_error_reason :password_authentication_required
56
57
  set_redirect_error_flash password_authentication_required_error_flash
57
58
  set_session_value(confirm_password_redirect_session_key, request.fullpath)
58
59
  redirect password_authentication_required_redirect
@@ -89,4 +90,3 @@ module Rodauth
89
90
  end
90
91
  end
91
92
  end
92
-