rodauth-rails 0.11.0 → 0.15.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (76) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +40 -0
  3. data/README.md +316 -218
  4. data/lib/generators/rodauth/templates/app/lib/rodauth_app.rb +8 -4
  5. data/lib/generators/rodauth/templates/app/models/account.rb +1 -0
  6. data/lib/generators/rodauth/templates/app/views/rodauth/_email_auth_request_form.html.erb +1 -1
  7. data/lib/generators/rodauth/templates/app/views/rodauth/_field.html.erb +2 -2
  8. data/lib/generators/rodauth/templates/app/views/rodauth/_field_error.html.erb +2 -2
  9. data/lib/generators/rodauth/templates/app/views/rodauth/_global_logout_field.html.erb +2 -2
  10. data/lib/generators/rodauth/templates/app/views/rodauth/_login_confirm_field.html.erb +3 -3
  11. data/lib/generators/rodauth/templates/app/views/rodauth/_login_display.html.erb +3 -3
  12. data/lib/generators/rodauth/templates/app/views/rodauth/_login_field.html.erb +3 -3
  13. data/lib/generators/rodauth/templates/app/views/rodauth/_login_form.html.erb +3 -3
  14. data/lib/generators/rodauth/templates/app/views/rodauth/_login_form_footer.html.erb +2 -2
  15. data/lib/generators/rodauth/templates/app/views/rodauth/_login_form_header.html.erb +2 -2
  16. data/lib/generators/rodauth/templates/app/views/rodauth/_login_hidden_field.html.erb +1 -1
  17. data/lib/generators/rodauth/templates/app/views/rodauth/_new_password_field.html.erb +3 -3
  18. data/lib/generators/rodauth/templates/app/views/rodauth/_otp_auth_code_field.html.erb +3 -3
  19. data/lib/generators/rodauth/templates/app/views/rodauth/_password_confirm_field.html.erb +3 -3
  20. data/lib/generators/rodauth/templates/app/views/rodauth/_password_field.html.erb +3 -3
  21. data/lib/generators/rodauth/templates/app/views/rodauth/_recovery_code_field.html.erb +3 -3
  22. data/lib/generators/rodauth/templates/app/views/rodauth/_recovery_codes_form.html.erb +4 -4
  23. data/lib/generators/rodauth/templates/app/views/rodauth/_sms_code_field.html.erb +3 -3
  24. data/lib/generators/rodauth/templates/app/views/rodauth/_sms_phone_field.html.erb +3 -3
  25. data/lib/generators/rodauth/templates/app/views/rodauth/_submit.html.erb +1 -1
  26. data/lib/generators/rodauth/templates/app/views/rodauth/add_recovery_codes.html.erb +2 -2
  27. data/lib/generators/rodauth/templates/app/views/rodauth/change_login.html.erb +3 -3
  28. data/lib/generators/rodauth/templates/app/views/rodauth/change_password.html.erb +3 -3
  29. data/lib/generators/rodauth/templates/app/views/rodauth/close_account.html.erb +2 -2
  30. data/lib/generators/rodauth/templates/app/views/rodauth/confirm_password.html.erb +1 -1
  31. data/lib/generators/rodauth/templates/app/views/rodauth/create_account.html.erb +4 -4
  32. data/lib/generators/rodauth/templates/app/views/rodauth/email_auth.html.erb +1 -1
  33. data/lib/generators/rodauth/templates/app/views/rodauth/logout.html.erb +2 -2
  34. data/lib/generators/rodauth/templates/app/views/rodauth/multi_phase_login.html.erb +1 -1
  35. data/lib/generators/rodauth/templates/app/views/rodauth/otp_auth.html.erb +1 -1
  36. data/lib/generators/rodauth/templates/app/views/rodauth/otp_disable.html.erb +2 -2
  37. data/lib/generators/rodauth/templates/app/views/rodauth/otp_setup.html.erb +9 -9
  38. data/lib/generators/rodauth/templates/app/views/rodauth/recovery_auth.html.erb +1 -1
  39. data/lib/generators/rodauth/templates/app/views/rodauth/remember.html.erb +5 -5
  40. data/lib/generators/rodauth/templates/app/views/rodauth/reset_password.html.erb +2 -2
  41. data/lib/generators/rodauth/templates/app/views/rodauth/reset_password_request.html.erb +2 -2
  42. data/lib/generators/rodauth/templates/app/views/rodauth/sms_auth.html.erb +1 -1
  43. data/lib/generators/rodauth/templates/app/views/rodauth/sms_confirm.html.erb +1 -1
  44. data/lib/generators/rodauth/templates/app/views/rodauth/sms_disable.html.erb +2 -2
  45. data/lib/generators/rodauth/templates/app/views/rodauth/sms_request.html.erb +1 -1
  46. data/lib/generators/rodauth/templates/app/views/rodauth/sms_setup.html.erb +2 -2
  47. data/lib/generators/rodauth/templates/app/views/rodauth/two_factor_auth.html.erb +1 -1
  48. data/lib/generators/rodauth/templates/app/views/rodauth/two_factor_disable.html.erb +2 -2
  49. data/lib/generators/rodauth/templates/app/views/rodauth/two_factor_manage.html.erb +6 -6
  50. data/lib/generators/rodauth/templates/app/views/rodauth/unlock_account.html.erb +2 -2
  51. data/lib/generators/rodauth/templates/app/views/rodauth/unlock_account_request.html.erb +1 -1
  52. data/lib/generators/rodauth/templates/app/views/rodauth/verify_account.html.erb +3 -3
  53. data/lib/generators/rodauth/templates/app/views/rodauth/verify_account_resend.html.erb +2 -2
  54. data/lib/generators/rodauth/templates/app/views/rodauth/verify_login_change.html.erb +1 -1
  55. data/lib/generators/rodauth/templates/app/views/rodauth/webauthn_auth.html.erb +7 -7
  56. data/lib/generators/rodauth/templates/app/views/rodauth/webauthn_remove.html.erb +6 -6
  57. data/lib/generators/rodauth/templates/app/views/rodauth/webauthn_setup.html.erb +7 -7
  58. data/lib/generators/rodauth/views_generator.rb +26 -4
  59. data/lib/rodauth/rails.rb +32 -13
  60. data/lib/rodauth/rails/auth.rb +9 -12
  61. data/lib/rodauth/rails/feature.rb +19 -230
  62. data/lib/rodauth/rails/feature/base.rb +62 -0
  63. data/lib/rodauth/rails/feature/callbacks.rb +65 -0
  64. data/lib/rodauth/rails/feature/csrf.rb +65 -0
  65. data/lib/rodauth/rails/feature/email.rb +30 -0
  66. data/lib/rodauth/rails/feature/instrumentation.rb +71 -0
  67. data/lib/rodauth/rails/feature/internal_request.rb +50 -0
  68. data/lib/rodauth/rails/feature/render.rb +48 -0
  69. data/lib/rodauth/rails/model.rb +101 -0
  70. data/lib/rodauth/rails/model/associations.rb +195 -0
  71. data/lib/rodauth/rails/railtie.rb +0 -5
  72. data/lib/rodauth/rails/tasks.rake +5 -5
  73. data/lib/rodauth/rails/version.rb +1 -1
  74. data/rodauth-rails.gemspec +4 -1
  75. metadata +56 -6
  76. data/lib/rodauth/rails/log_subscriber.rb +0 -34
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b8063be8ad00634114f74f0eb549c672e2b62cd1fa81cb7f124cc9cd12505e3f
4
- data.tar.gz: 6f466e29420f9e4bacb58c855e942cc20289d2c3fc69a12638b97628d25dbbfb
3
+ metadata.gz: '097625662e9cefbf7484ea775c9b1930968fbbc3a1b8ad24df9e229e2194e301'
4
+ data.tar.gz: fbbfe9dd849646e859aecbf3c600168fc0b65255f7b2bf933a2e42591f8b0b79
5
5
  SHA512:
6
- metadata.gz: 8cc0af59c6ce29837fbc8a3401d456fd407ef76b74493b08ee9b4f2dfc8807d4a95c86f9bb0266401013d5162c009d46b7d07e3f741654af2cc267c0ee2c135e
7
- data.tar.gz: 78c098dbaed458d5764ca2e7ee61f4710e01b2386d0cc04831b1732b9883d76c4b9f56c35c0a1e557c40951086d72bb0ed264f769313c1b45be30b2dd760024a
6
+ metadata.gz: 762d0c1725dcd0017cdd6722894e546dfdf246e245af688a8bc99f177e43765fe8bd7a79639e45d3146ca5bedadce9f34389f61bf3a6957b406cbc664cf93829
7
+ data.tar.gz: 8c6624c70668356b8434dde9bd237cced89f23d4d241b770989f1af68ee687b7186f305ada409885d619b9974e7d2d47b6fddc9faf6935653cab805ee8709167
data/CHANGELOG.md CHANGED
@@ -1,3 +1,43 @@
1
+ ## 0.15.0 (2021-07-29)
2
+
3
+ * Add `Rodauth::Rails::Model` mixin that defines password attribute and associations on the model (@janko)
4
+
5
+ * Add support for the new internal_request feature (@janko)
6
+
7
+ * Implement `Rodauth::Rails.rodauth` in terms of the internal_request feature (@janko)
8
+
9
+ ## 0.14.0 (2021-07-10)
10
+
11
+ * Speed up template rendering by only searching formats accepted by the request (@janko)
12
+
13
+ * Add `--name` option to `rodauth:views` generator for specifying different rodauth configuration (@janko)
14
+
15
+ * Infer correct template path from configured controller in `rodauth:views` generator (@janko)
16
+
17
+ * Raise `ArgumentError` if undefined rodauth configuration is passed to `Rodauth::Rails.app` (@janko)
18
+
19
+ * Make `#rails_controller` method on the rodauth instance public (@janko)
20
+
21
+ * Remove `--directory` option from `rodauth:views` generator (@janko)
22
+
23
+ * Remove `#features` and `#routes` writer and `#configuration` reader from `Rodauth::Rails::Auth` (@janko)
24
+
25
+ ## 0.13.0 (2021-06-10)
26
+
27
+ * Add `:query`, `:form`, `:session`, `:account`, and `:env` options to `Rodauth::Rails.rodauth` (@janko)
28
+
29
+ ## 0.12.0 (2021-05-15)
30
+
31
+ * Include total view render time in logs for Rodauth requests (@janko)
32
+
33
+ * Instrument redirects (@janko)
34
+
35
+ * Instrument Rodauth requests on `action_controller` namespace (@janko)
36
+
37
+ * Update templates for Boostrap 5 compatibility (@janko)
38
+
39
+ * Log request parameters for Rodauth requests (@janko)
40
+
1
41
  ## 0.11.0 (2021-05-06)
2
42
 
3
43
  * Add controller-like logging for requests to Rodauth endpoints (@janko)
data/README.md CHANGED
@@ -41,27 +41,15 @@ Active Record's database connection][sequel-activerecord_connection].
41
41
 
42
42
  ## Upgrading
43
43
 
44
- ### Upgrading to 0.7.0
45
-
46
- Starting from version 0.7.0, rodauth-rails now correctly detects Rails
47
- application's `secret_key_base` when setting default `hmac_secret`, including
48
- when it's set via credentials or `$SECRET_KEY_BASE` environment variable. This
49
- means that your authentication will now be more secure by default, and Rodauth
50
- features that require `hmac_secret` should now work automatically as well.
51
-
52
- However, if you've already been using rodauth-rails in production, where the
53
- `secret_key_base` is set via credentials or environment variable and `hmac_secret`
54
- was not explicitly set, the fact that your authentication will now start using
55
- HMACs has backwards compatibility considerations. See the [Rodauth
56
- documentation][hmac] for instructions on how to safely transition, or just set
57
- `hmac_secret nil` in your Rodauth configuration.
44
+ For instructions on upgrading from previous rodauth-rails versions, see
45
+ [UPGRADING.md](/UPGRADING.md).
58
46
 
59
47
  ## Installation
60
48
 
61
49
  Add the gem to your Gemfile:
62
50
 
63
51
  ```rb
64
- gem "rodauth-rails", "~> 0.10"
52
+ gem "rodauth-rails", "~> 0.15"
65
53
 
66
54
  # gem "jwt", require: false # for JWT feature
67
55
  # gem "rotp", require: false # for OTP feature
@@ -86,132 +74,22 @@ $ rails generate rodauth:install --jwt # token authentication via the "Authoriza
86
74
  $ bundle add jwt
87
75
  ```
88
76
 
89
- The generator will create the following files:
90
-
91
- * Rodauth migration at `db/migrate/*_create_rodauth.rb`
92
- * Rodauth initializer at `config/initializers/rodauth.rb`
93
- * Sequel initializer at `config/initializers/sequel.rb` for ActiveRecord integration
94
- * Rodauth app at `app/lib/rodauth_app.rb`
95
- * Rodauth controller at `app/controllers/rodauth_controller.rb`
96
- * Account model at `app/models/account.rb`
97
- * Rodauth mailer at `app/mailers/rodauth_mailer.rb` with views
98
-
99
- ### Migration
100
-
101
- The migration file creates tables required by Rodauth. You're encouraged to
102
- review the migration, and modify it to only create tables for features you
103
- intend to use.
104
-
105
- ```rb
106
- # db/migrate/*_create_rodauth.rb
107
- class CreateRodauth < ActiveRecord::Migration
108
- def change
109
- create_table :accounts do |t| ... end
110
- create_table :account_password_hashes do |t| ... end
111
- create_table :account_password_reset_keys do |t| ... end
112
- create_table :account_verification_keys do |t| ... end
113
- create_table :account_login_change_keys do |t| ... end
114
- create_table :account_remember_keys do |t| ... end
115
- end
116
- end
117
- ```
77
+ This generator will create a Rodauth app with common authentication features
78
+ enabled, a database migration with tables required by those features, a mailer
79
+ with default templates, and a few other files.
118
80
 
119
- Once you're done, you can run the migration:
81
+ Feel free to remove any features you don't need, along with their corresponding
82
+ tables. Afterwards, run the migration:
120
83
 
121
- ```
84
+ ```sh
122
85
  $ rails db:migrate
123
86
  ```
124
87
 
125
- ### Rodauth initializer
126
-
127
- The Rodauth initializer assigns the constant for your Rodauth app, which will
128
- be called by the Rack middleware that's added in front of your Rails router.
129
-
130
- ```rb
131
- # config/initializers/rodauth.rb
132
- Rodauth::Rails.configure do |config|
133
- config.app = "RodauthApp"
134
- end
135
- ```
136
-
137
- ### Sequel initializer
138
-
139
- Rodauth uses [Sequel] for database interaction. If you're using ActiveRecord,
140
- an additional initializer will be created which configures Sequel to use the
141
- ActiveRecord connection.
142
-
143
- ```rb
144
- # config/initializers/sequel.rb
145
- require "sequel/core"
146
-
147
- # initialize Sequel and have it reuse Active Record's database connection
148
- DB = Sequel.connect("postgresql://", extensions: :activerecord_connection)
149
- ```
150
-
151
- ### Rodauth app
152
-
153
- Your Rodauth app is created in the `app/lib/` directory, and comes with a
154
- default set of authentication features enabled, as well as extensive examples
155
- on ways you can configure authentication behaviour.
156
-
157
- ```rb
158
- # app/lib/rodauth_app.rb
159
- class RodauthApp < Rodauth::Rails::App
160
- configure do
161
- # authentication configuration
162
- end
163
-
164
- route do |r|
165
- # request handling
166
- end
167
- end
168
- ```
169
-
170
- ### Controller
171
-
172
- Your Rodauth app will by default use `RodauthController` for view rendering,
173
- CSRF protection, and running controller callbacks and rescue handlers around
174
- Rodauth actions.
175
-
176
- ```rb
177
- # app/controllers/rodauth_controller.rb
178
- class RodauthController < ApplicationController
179
- end
180
- ```
181
-
182
- ### Account model
183
-
184
- Rodauth stores user accounts in the `accounts` table, so the generator will
185
- also create an `Account` model for custom use.
186
-
187
- ```rb
188
- # app/models/account.rb
189
- class Account < ApplicationRecord
190
- end
191
- ```
192
-
193
- ### Rodauth mailer
194
-
195
- The default Rodauth app is configured to use `RodauthMailer` mailer
196
- for sending authentication emails.
197
-
198
- ```rb
199
- # app/mailers/rodauth_mailer.rb
200
- class RodauthMailer < ApplicationMailer
201
- def verify_account(recipient, email_link) ... end
202
- def reset_password(recipient, email_link) ... end
203
- def verify_login_change(recipient, old_login, new_login, email_link) ... end
204
- def password_changed(recipient) ... end
205
- # def email_auth(recipient, email_link) ... end
206
- # def unlock_account(recipient, email_link) ... end
207
- end
208
- ```
209
-
210
88
  ## Usage
211
89
 
212
90
  ### Routes
213
91
 
214
- We can see the list of routes our Rodauth middleware handles:
92
+ You can see the list of routes our Rodauth middleware handles:
215
93
 
216
94
  ```sh
217
95
  $ rails rodauth:routes
@@ -233,7 +111,7 @@ Routes handled by RodauthApp:
233
111
  /close-account rodauth.close_account_path
234
112
  ```
235
113
 
236
- Using this information, we could add some basic authentication links to our
114
+ Using this information, you can add some basic authentication links to your
237
115
  navigation header:
238
116
 
239
117
  ```erb
@@ -264,7 +142,7 @@ end
264
142
 
265
143
  ### Current account
266
144
 
267
- To be able to fetch currently authenticated account, let's define a
145
+ To be able to fetch currently authenticated account, you can define a
268
146
  `#current_account` method that fetches the account id from session and
269
147
  retrieves the corresponding account record:
270
148
 
@@ -281,11 +159,11 @@ class ApplicationController < ActionController::Base
281
159
  rodauth.logout
282
160
  rodauth.login_required
283
161
  end
284
- helper_method :current_account # skip if inheriting from ActionController:API
162
+ helper_method :current_account # skip if inheriting from ActionController::API
285
163
  end
286
164
  ```
287
165
 
288
- This allows us to access the current account in controllers and views:
166
+ This allows you to access the current account in controllers and views:
289
167
 
290
168
  ```erb
291
169
  <p>Authenticated as: <%= current_account.email %></p>
@@ -293,9 +171,9 @@ This allows us to access the current account in controllers and views:
293
171
 
294
172
  ### Requiring authentication
295
173
 
296
- We'll likely want to require authentication for certain parts of our app,
297
- redirecting the user to the login page if they're not logged in. We can do this
298
- in our Rodauth app's routing block, which helps keep the authentication logic
174
+ You'll likely want to require authentication for certain parts of your app,
175
+ redirecting the user to the login page if they're not logged in. You can do this
176
+ in your Rodauth app's routing block, which helps keep the authentication logic
299
177
  encapsulated:
300
178
 
301
179
  ```rb
@@ -314,7 +192,7 @@ class RodauthApp < Rodauth::Rails::App
314
192
  end
315
193
  ```
316
194
 
317
- We can also require authentication at the controller layer:
195
+ You can also require authentication at the controller layer:
318
196
 
319
197
  ```rb
320
198
  # app/controllers/application_controller.rb
@@ -341,8 +219,8 @@ end
341
219
 
342
220
  #### Routing constraints
343
221
 
344
- You can also require authentication at the Rails router level by
345
- using a built-in `authenticated` routing constraint:
222
+ In some cases it makes sense to require authentication at the Rails router
223
+ level. You can do this via the built-in `authenticated` routing constraint:
346
224
 
347
225
  ```rb
348
226
  # config/routes.rb
@@ -400,11 +278,11 @@ $ rails generate rodauth:views
400
278
  ```
401
279
 
402
280
  This will generate views for the default set of Rodauth features into the
403
- `app/views/rodauth` directory, which will be automatically picked up by the
404
- `RodauthController`.
281
+ `app/views/rodauth` directory, provided that `RodauthController` is set for the
282
+ main configuration.
405
283
 
406
284
  You can pass a list of Rodauth features to the generator to create views for
407
- these features (this will not remove any existing views):
285
+ these features (this will not remove or overwrite any existing views):
408
286
 
409
287
  ```sh
410
288
  $ rails generate rodauth:views login create_account lockout otp
@@ -416,12 +294,10 @@ Or you can generate views for all features:
416
294
  $ rails generate rodauth:views --all
417
295
  ```
418
296
 
419
- You can also tell the generator to create views into another directory (in this
420
- case make sure to rename the Rodauth controller accordingly):
297
+ Use `--name` to generate views for a different Rodauth configuration:
421
298
 
422
299
  ```sh
423
- # generates views into app/views/authentication
424
- $ rails generate rodauth:views --name authentication
300
+ $ rails generate rodauth:views --name admin
425
301
  ```
426
302
 
427
303
  #### Layout
@@ -514,14 +390,48 @@ end
514
390
  This configuration calls `#deliver_later`, which uses Active Job to deliver
515
391
  emails in a background job. It's generally recommended to send emails
516
392
  asynchronously for better request throughput and the ability to retry
517
- deliveries. However, if you want to send emails synchronously, modify the
518
- configuration to call `#deliver_now` instead.
393
+ deliveries. However, if you want to send emails synchronously, you can modify
394
+ the configuration to call `#deliver_now` instead.
519
395
 
520
396
  If you're using a background processing library without an Active Job adapter,
521
397
  or a 3rd-party service for sending transactional emails, this two-phase API
522
398
  might not be suitable. In this case, instead of overriding `#create_*_email`
523
399
  and `#send_email`, override the `#send_*_email` methods instead, which are
524
- required to send the email immediately.
400
+ required to send the email immediately. For example:
401
+
402
+ ```rb
403
+ # app/workers/rodauth_mailer_worker.rb
404
+ class RodauthMailerWorker
405
+ include Sidekiq::Worker
406
+
407
+ def perform(name, *args)
408
+ email = RodauthMailer.public_send(name, *args)
409
+ email.deliver_now
410
+ end
411
+ end
412
+ ```
413
+ ```rb
414
+ # app/lib/rodauth_app.rb
415
+ class RodauthApp < Rodauth::Rails::App
416
+ configure do
417
+ # ...
418
+ # use `#send_*_email` method to be able to immediately enqueue email delivery
419
+ send_reset_password_email do
420
+ enqueue_email(:reset_password, email_to, reset_password_email_link)
421
+ end
422
+ # ...
423
+ auth_class_eval do
424
+ # custom method for enqueuing email delivery using our worker
425
+ def enqueue_email(name, *args)
426
+ db.after_commit do
427
+ RodauthMailerWorker.perform_async(name, *args)
428
+ end
429
+ end
430
+ end
431
+ # ...
432
+ end
433
+ end
434
+ ```
525
435
 
526
436
  ### Migrations
527
437
 
@@ -543,10 +453,134 @@ class CreateRodauthOtpSmsCodesRecoveryCodes < ActiveRecord::Migration
543
453
  end
544
454
  ```
545
455
 
456
+ ### Model
457
+
458
+ The `Rodauth::Rails::Model` mixin can be included into the account model, which
459
+ defines a password attribute and associations for tables used by enabled
460
+ authentication features.
461
+
462
+ ```rb
463
+ class Account < ApplicationRecord
464
+ include Rodauth::Rails.model # or `Rodauth::Rails.model(:admin)`
465
+ end
466
+ ```
467
+
468
+ #### Password attribute
469
+
470
+ Regardless of whether you're storing the password hash in a column in the
471
+ accounts table, or in a separate table, the `#password` attribute can be used
472
+ to set or clear the password hash.
473
+
474
+ ```rb
475
+ account = Account.create!(email: "user@example.com", password: "secret")
476
+
477
+ # when password hash is stored in a column on the accounts table
478
+ account.password_hash #=> "$2a$12$k/Ub1I2iomi84RacqY89Hu4.M0vK7klRnRtzorDyvOkVI.hKhkNw."
479
+
480
+ # when password hash is stored in a separate table
481
+ account.password_hash #=> #<Account::PasswordHash...> (record from `account_password_hashes` table)
482
+ account.password_hash.password_hash #=> "$2a$12$k/Ub1..." (inaccessible when using database authentication functions)
483
+
484
+ account.password = nil # clears password hash
485
+ account.password_hash #=> nil
486
+ ```
487
+
488
+ Note that the password attribute doesn't come with validations, making it
489
+ unsuitable for forms. It was primarily intended to allow easily creating
490
+ accounts in development console and in tests.
491
+
492
+ #### Associations
493
+
494
+ The `Rodauth::Rails::Model` mixin defines associations for Rodauth tables
495
+ associated to the accounts table:
496
+
497
+ ```rb
498
+ account.remember_key #=> #<Account::RememberKey> (record from `account_remember_keys` table)
499
+ account.active_session_keys #=> [#<Account::ActiveSessionKey>,...] (records from `account_active_session_keys` table)
500
+ ```
501
+
502
+ You can also reference the associated models directly:
503
+
504
+ ```rb
505
+ # model referencing the `account_authentication_audit_logs` table
506
+ Account::AuthenticationAuditLog.where(message: "login").group(:account_id)
507
+ ```
508
+
509
+ The associated models define the inverse `belongs_to :account` association:
510
+
511
+ ```rb
512
+ Account::ActiveSessionKey.includes(:account).map(&:account)
513
+ ```
514
+
515
+ Here is an example of using associations to create a method that returns
516
+ whether the account has multifactor authentication enabled:
517
+
518
+ ```rb
519
+ class Account < ApplicationRecord
520
+ include Rodauth::Rails.model
521
+
522
+ def mfa_enabled?
523
+ otp_key || (sms_code && sms_code.num_failures.nil?) || recovery_codes.any?
524
+ end
525
+ end
526
+ ```
527
+
528
+ Here is another example of creating a query scope that selects accounts with
529
+ multifactor authentication enabled:
530
+
531
+ ```rb
532
+ class Account < ApplicationRecord
533
+ include Rodauth::Rails.model
534
+
535
+ scope :otp_setup, -> { where(otp_key: OtpKey.all) }
536
+ scope :sms_codes_setup, -> { where(sms_code: SmsCode.where(num_failures: nil)) }
537
+ scope :recovery_codes_setup, -> { where(recovery_codes: RecoveryCode.all) }
538
+ scope :mfa_enabled, -> { merge(otp_setup.or(sms_codes_setup).or(recovery_codes_setup)) }
539
+ end
540
+ ```
541
+
542
+ Below is a list of all associations defined depending on the features loaded:
543
+
544
+ | Feature | Association | Type | Model | Table (default) |
545
+ | :------ | :---------- | :--- | :---- | :---- |
546
+ | account_expiration | `:activity_time` | `has_one` | `ActivityTime` | `account_activity_times` |
547
+ | active_sessions | `:active_session_keys` | `has_many` | `ActiveSessionKey` | `account_active_session_keys` |
548
+ | audit_logging | `:authentication_audit_logs` | `has_many` | `AuthenticationAuditLog` | `account_authentication_audit_logs` |
549
+ | disallow_password_reuse | `:previous_password_hashes` | `has_many` | `PreviousPasswordHash` | `account_previous_password_hashes` |
550
+ | email_auth | `:email_auth_key` | `has_one` | `EmailAuthKey` | `account_email_auth_keys` |
551
+ | jwt_refresh | `:jwt_refresh_keys` | `has_many` | `JwtRefreshKey` | `account_jwt_refresh_keys` |
552
+ | lockout | `:lockout` | `has_one` | `Lockout` | `account_lockouts` |
553
+ | lockout | `:login_failure` | `has_one` | `LoginFailure` | `account_login_failures` |
554
+ | otp | `:otp_key` | `has_one` | `OtpKey` | `account_otp_keys` |
555
+ | password_expiration | `:password_change_time` | `has_one` | `PasswordChangeTime` | `account_password_change_times` |
556
+ | recovery_codes | `:recovery_codes` | `has_many` | `RecoveryCode` | `account_recovery_codes` |
557
+ | remember | `:remember_key` | `has_one` | `RememberKey` | `account_remember_keys` |
558
+ | reset_password | `:password_reset_key` | `has_one` | `PasswordResetKey` | `account_password_reset_keys` |
559
+ | single_session | `:session_key` | `has_one` | `SessionKey` | `account_session_keys` |
560
+ | sms_codes | `:sms_code` | `has_one` | `SmsCode` | `account_sms_codes` |
561
+ | verify_account | `:verification_key` | `has_one` | `VerificationKey` | `account_verification_keys` |
562
+ | verify_login_change | `:login_change_key` | `has_one` | `LoginChangeKey` | `account_login_change_keys` |
563
+ | webauthn | `:webauthn_keys` | `has_many` | `WebauthnKey` | `account_webauthn_keys` |
564
+ | webauthn | `:webauthn_user_id` | `has_one` | `WebauthnUserId` | `account_webauthn_user_ids` |
565
+
566
+ By default, all associations except for audit logs have `dependent: :destroy`
567
+ set, to allow for easy deletion of account records in the console. You can use
568
+ `:association_options` to modify global or per-association options:
569
+
570
+ ```rb
571
+ # don't auto-delete associations when account model is deleted
572
+ Rodauth::Rails.model(association_options: { dependent: nil })
573
+
574
+ # require authentication audit logs to be eager loaded before retrieval
575
+ Rodauth::Rails.model(association_options: -> (name) {
576
+ { strict_loading: true } if name == :authentication_audit_logs
577
+ })
578
+ ```
579
+
546
580
  ### Multiple configurations
547
581
 
548
582
  If you need to handle multiple types of accounts that require different
549
- authentication logic, you can create different configurations for them:
583
+ authentication logic, you can create additional configurations for them:
550
584
 
551
585
  ```rb
552
586
  # app/lib/rodauth_app.rb
@@ -562,10 +596,6 @@ class RodauthApp < Rodauth::Rails::App
562
596
  prefix "/admin"
563
597
  session_key_prefix "admin_"
564
598
  remember_cookie_key "_admin_remember" # if using remember feature
565
-
566
- # if you want separate tables
567
- accounts_table :admin_accounts
568
- password_hash_table :admin_account_password_hashes
569
599
  # ...
570
600
  end
571
601
 
@@ -574,7 +604,7 @@ class RodauthApp < Rodauth::Rails::App
574
604
 
575
605
  r.on "admin" do
576
606
  r.rodauth(:admin)
577
- r.pass # allow the Rails app to handle other "/admin/*" requests
607
+ break # allow routing of other /admin/* requests to continue to Rails
578
608
  end
579
609
 
580
610
  # ...
@@ -588,6 +618,50 @@ Then in your application you can reference the secondary Rodauth instance:
588
618
  rodauth(:admin).login_path #=> "/admin/login"
589
619
  ```
590
620
 
621
+ You'll likely want to save the information of which account belongs to which
622
+ configuration to the database. One way would be to have a separate table that
623
+ stores account types:
624
+
625
+ ```sh
626
+ $ rails generate migration create_account_types
627
+ ```
628
+ ```rb
629
+ # db/migrate/*_create_account_types.rb
630
+ class CreateAccountTypes < ActiveRecord::Migration
631
+ def change
632
+ create_table :account_types do |t|
633
+ t.references :account, foreign_key: { on_delete: :cascade }, null: false
634
+ t.string :type, null: false
635
+ end
636
+ end
637
+ end
638
+ ```
639
+ ```sh
640
+ $ rails db:migrate
641
+ ```
642
+
643
+ Then an entry would be inserted after account creation, and optionally whenever
644
+ Rodauth retrieves accounts you could filter only those belonging to the current
645
+ configuration:
646
+
647
+ ```rb
648
+ # app/lib/rodauth_app.rb
649
+ class RodauthApp < Rodauth::Rails::App
650
+ configure(:admin) do
651
+ # ...
652
+ after_create_account do
653
+ db[:account_types].insert(account_id: account_id, type: "admin")
654
+ end
655
+ auth_class_eval do
656
+ def account_ds(*)
657
+ super.join(:account_types, account_id: :id).where(type: "admin")
658
+ end
659
+ end
660
+ # ...
661
+ end
662
+ end
663
+ ```
664
+
591
665
  #### Named auth classes
592
666
 
593
667
  A `configure` block inside `Rodauth::Rails::App` will internally create an
@@ -656,8 +730,8 @@ class RodauthAdmin < RodauthBase # inherit common settings
656
730
  end
657
731
  ```
658
732
 
659
- Another benefit is that you can define custom methods directly on the class
660
- instead of through `auth_class_eval`:
733
+ Another benefit of explicit classes is that you can define custom methods
734
+ directly at the class level instead of inside an `auth_class_eval`:
661
735
 
662
736
  ```rb
663
737
  # app/lib/rodauth_admin.rb
@@ -706,24 +780,54 @@ class RodauthApp < Rodauth::Rails::App
706
780
  end
707
781
  ```
708
782
 
709
- ### Rodauth instance
783
+ ### Outside of a request
784
+
785
+ In some cases you might need to use Rodauth more programmatically. If you would
786
+ like to perform Rodauth operations outside of request context, Rodauth ships
787
+ with the [internal_request] feature just for that. The rodauth-rails gem
788
+ additionally updates the internal rack env hash with your
789
+ `config.action_mailer.default_url_options`, which is used for generating URLs.
710
790
 
711
- In some cases you might need to use Rodauth more programmatically, and perform
712
- Rodauth operations outside of the request context. rodauth-rails gives you the
713
- ability to retrieve the Rodauth instance:
791
+ If you need to access Rodauth methods not exposed as internal requests, you can
792
+ use `Rodauth::Rails.rodauth` to retrieve the Rodauth instance used by the
793
+ internal_request feature:
714
794
 
715
795
  ```rb
716
- rodauth = Rodauth::Rails.rodauth # or Rodauth::Rails.rodauth(:admin)
796
+ # app/lib/rodauth_app.rb
797
+ class RodauthApp < Rodauth::Rails::App
798
+ configure do
799
+ enable :internal_request # this is required
800
+ end
801
+ end
802
+ ```
803
+ ```rb
804
+ account = Account.find_by!(email: "user@example.com")
805
+ rodauth = Rodauth::Rails.rodauth(account: account)
806
+
807
+ rodauth.compute_hmac("token") #=> "TpEJTKfKwqYvIDKWsuZhkhKlhaBXtR1aodskBAflD8U"
808
+ rodauth.open_account? #=> true
809
+ rodauth.two_factor_authentication_setup? #=> true
810
+ rodauth.password_meets_requirements?("foo") #=> false
811
+ rodauth.locked_out? #=> false
812
+ ```
813
+
814
+ In addition to the `:account` option, the `Rodauth::Rails.rodauth`
815
+ method accepts any options supported by the internal_request feature.
717
816
 
718
- rodauth.login_url #=> "https://example.com/login"
719
- rodauth.account_from_login("user@example.com") # loads user by email
720
- rodauth.password_match?("secret") #=> true
721
- rodauth.setup_account_verification
722
- rodauth.close_account
817
+ ```rb
818
+ Rodauth::Rails.rodauth(
819
+ env: { "HTTP_USER_AGENT" => "programmatic" },
820
+ session: { two_factor_auth_setup: true },
821
+ params: { "param" => "value" }
822
+ )
723
823
  ```
724
824
 
725
- This Rodauth instance will be initialized with basic Rack env that allows is it
726
- to generate URLs, using `config.action_mailer.default_url_options` options.
825
+ Secondary Rodauth configurations are specified by passing the configuration
826
+ name:
827
+
828
+ ```rb
829
+ Rodauth::Rails.rodauth(:admin)
830
+ ```
727
831
 
728
832
  ## How it works
729
833
 
@@ -834,7 +938,7 @@ class RodauthApp < Rodauth::Rails::App
834
938
  configure do
835
939
  # ...
836
940
  enable :json
837
- only_json? true # accept only JSON requests
941
+ only_json? true # accept only JSON requests (optional)
838
942
  # ...
839
943
  end
840
944
  end
@@ -855,27 +959,29 @@ class RodauthApp < Rodauth::Rails::App
855
959
  # ...
856
960
  enable :jwt
857
961
  jwt_secret "<YOUR_SECRET_KEY>" # store the JWT secret in a safe place
858
- only_json? true # accept only JSON requests
962
+ only_json? true # accept only JSON requests (optional)
859
963
  # ...
860
964
  end
861
965
  end
862
966
  ```
863
967
 
864
- If you need Cross-Origin Resource Sharing and/or JWT refresh tokens, enable the
865
- corresponding Rodauth features and create the necessary tables:
968
+ The JWT token will be returned after each request to Rodauth routes. To also
969
+ return the JWT token on requests to your app's routes, you can add the
970
+ following code to your base controller:
866
971
 
867
- ```sh
868
- $ rails generate rodauth:migration jwt_refresh
869
- $ rails db:migrate
870
- ```
871
972
  ```rb
872
- # app/lib/rodauth_app.rb
873
- class RodauthApp < Rodauth::Rails::App
874
- configure do
875
- # ...
876
- enable :jwt, :jwt_cors, :jwt_refresh
877
- # ...
973
+ class ApplicationController < ActionController::Base
974
+ # ...
975
+ after_action :set_jwt_token
976
+
977
+ private
978
+
979
+ def set_jwt_token
980
+ if rodauth.use_jwt? && rodauth.valid_jwt?
981
+ response.headers["Authorization"] = rodauth.session_jwt
982
+ end
878
983
  end
984
+ # ...
879
985
  end
880
986
  ```
881
987
 
@@ -935,7 +1041,8 @@ end
935
1041
  <%= link_to "Login via Facebook", "/auth/facebook" %>
936
1042
  ```
937
1043
 
938
- Let's implement the OmniAuth callback endpoint on our Rodauth controller:
1044
+ Finally, let's implement the OmniAuth callback endpoint on our Rodauth
1045
+ controller:
939
1046
 
940
1047
  ```rb
941
1048
  # config/routes.rb
@@ -988,11 +1095,8 @@ end
988
1095
 
989
1096
  ## Configuring
990
1097
 
991
- For the list of configuration methods provided by Rodauth, see the [feature
992
- documentation].
993
-
994
- The `rails` feature rodauth-rails loads is customizable as well, here is the
995
- list of its configuration methods:
1098
+ The `rails` feature rodauth-rails loads provides the following configuration
1099
+ methods:
996
1100
 
997
1101
  | Name | Description |
998
1102
  | :---- | :---------- |
@@ -1019,12 +1123,16 @@ Rodauth::Rails.configure do |config|
1019
1123
  end
1020
1124
  ```
1021
1125
 
1126
+ For the list of configuration methods provided by Rodauth, see the [feature
1127
+ documentation].
1128
+
1022
1129
  ## Custom extensions
1023
1130
 
1024
1131
  When developing custom extensions for Rodauth inside your Rails project, it's
1025
- better to use plain modules (at least in the beginning), because Rodauth
1026
- feature design doesn't yet support Zeitwerk reloading well. Here is
1027
- an example of an LDAP authentication extension that uses the
1132
+ probably better to use plain modules, at least in the beginning, as Rodauth
1133
+ feature design doesn't yet work well with Zeitwerk reloading.
1134
+
1135
+ Here is an example of an LDAP authentication extension that uses the
1028
1136
  [simple_ldap_authenticator] gem.
1029
1137
 
1030
1138
  ```rb
@@ -1181,29 +1289,6 @@ Rails.application.configure do |config|
1181
1289
  end
1182
1290
  ```
1183
1291
 
1184
- If you need to create an account record with a password directly, you can do it
1185
- as follows:
1186
-
1187
- ```rb
1188
- # app/models/account.rb
1189
- class Account < ApplicationRecord
1190
- has_one :password_hash, foreign_key: :id
1191
- end
1192
- ```
1193
- ```rb
1194
- # app/models/account/password_hash.rb
1195
- class Account::PasswordHash < ApplicationRecord
1196
- belongs_to :account, foreign_key: :id
1197
- end
1198
- ```
1199
- ```rb
1200
- require "bcrypt"
1201
-
1202
- account = Account.create!(email: "user@example.com", status: "verified")
1203
- password_hash = BCrypt::Password.create("secret", cost: BCrypt::Engine::MIN_COST)
1204
- account.create_password_hash!(id: account.id, password_hash: password_hash)
1205
- ```
1206
-
1207
1292
  ## Rodauth defaults
1208
1293
 
1209
1294
  rodauth-rails changes some of the default Rodauth settings for easier setup:
@@ -1284,6 +1369,18 @@ configure do
1284
1369
  end
1285
1370
  ```
1286
1371
 
1372
+ ### Deadline values
1373
+
1374
+ To simplify changes to the database schema, rodauth-rails configures Rodauth
1375
+ to set deadline values for various features in Ruby, instead of relying on
1376
+ the database to set default column values.
1377
+
1378
+ You can easily change this back:
1379
+
1380
+ ```rb
1381
+ set_deadline_values? false
1382
+ ```
1383
+
1287
1384
  ## License
1288
1385
 
1289
1386
  The gem is available as open source under the terms of the [MIT
@@ -1325,3 +1422,4 @@ conduct](https://github.com/janko/rodauth-rails/blob/master/CODE_OF_CONDUCT.md).
1325
1422
  [single_session]: http://rodauth.jeremyevans.net/rdoc/files/doc/single_session_rdoc.html
1326
1423
  [account_expiration]: http://rodauth.jeremyevans.net/rdoc/files/doc/account_expiration_rdoc.html
1327
1424
  [simple_ldap_authenticator]: https://github.com/jeremyevans/simple_ldap_authenticator
1425
+ [internal_request]: http://rodauth.jeremyevans.net/rdoc/files/doc/internal_request_rdoc.html