rodauth-rails 0.12.0 → 0.16.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.
Files changed (71) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +36 -0
  3. data/README.md +304 -98
  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 +1 -1
  10. data/lib/generators/rodauth/templates/app/views/rodauth/_login_confirm_field.html.erb +1 -1
  11. data/lib/generators/rodauth/templates/app/views/rodauth/_login_display.html.erb +1 -1
  12. data/lib/generators/rodauth/templates/app/views/rodauth/_login_field.html.erb +1 -1
  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 +1 -1
  18. data/lib/generators/rodauth/templates/app/views/rodauth/_otp_auth_code_field.html.erb +1 -1
  19. data/lib/generators/rodauth/templates/app/views/rodauth/_password_confirm_field.html.erb +1 -1
  20. data/lib/generators/rodauth/templates/app/views/rodauth/_password_field.html.erb +1 -1
  21. data/lib/generators/rodauth/templates/app/views/rodauth/_recovery_code_field.html.erb +1 -1
  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 +1 -1
  24. data/lib/generators/rodauth/templates/app/views/rodauth/_sms_phone_field.html.erb +1 -1
  25. data/lib/generators/rodauth/templates/app/views/rodauth/add_recovery_codes.html.erb +2 -2
  26. data/lib/generators/rodauth/templates/app/views/rodauth/change_login.html.erb +3 -3
  27. data/lib/generators/rodauth/templates/app/views/rodauth/change_password.html.erb +3 -3
  28. data/lib/generators/rodauth/templates/app/views/rodauth/close_account.html.erb +2 -2
  29. data/lib/generators/rodauth/templates/app/views/rodauth/confirm_password.html.erb +1 -1
  30. data/lib/generators/rodauth/templates/app/views/rodauth/create_account.html.erb +4 -4
  31. data/lib/generators/rodauth/templates/app/views/rodauth/email_auth.html.erb +1 -1
  32. data/lib/generators/rodauth/templates/app/views/rodauth/logout.html.erb +2 -2
  33. data/lib/generators/rodauth/templates/app/views/rodauth/multi_phase_login.html.erb +1 -1
  34. data/lib/generators/rodauth/templates/app/views/rodauth/otp_auth.html.erb +1 -1
  35. data/lib/generators/rodauth/templates/app/views/rodauth/otp_disable.html.erb +2 -2
  36. data/lib/generators/rodauth/templates/app/views/rodauth/otp_setup.html.erb +7 -7
  37. data/lib/generators/rodauth/templates/app/views/rodauth/recovery_auth.html.erb +1 -1
  38. data/lib/generators/rodauth/templates/app/views/rodauth/remember.html.erb +4 -4
  39. data/lib/generators/rodauth/templates/app/views/rodauth/reset_password.html.erb +2 -2
  40. data/lib/generators/rodauth/templates/app/views/rodauth/reset_password_request.html.erb +2 -2
  41. data/lib/generators/rodauth/templates/app/views/rodauth/sms_auth.html.erb +1 -1
  42. data/lib/generators/rodauth/templates/app/views/rodauth/sms_confirm.html.erb +1 -1
  43. data/lib/generators/rodauth/templates/app/views/rodauth/sms_disable.html.erb +2 -2
  44. data/lib/generators/rodauth/templates/app/views/rodauth/sms_request.html.erb +1 -1
  45. data/lib/generators/rodauth/templates/app/views/rodauth/sms_setup.html.erb +2 -2
  46. data/lib/generators/rodauth/templates/app/views/rodauth/two_factor_auth.html.erb +1 -1
  47. data/lib/generators/rodauth/templates/app/views/rodauth/two_factor_disable.html.erb +2 -2
  48. data/lib/generators/rodauth/templates/app/views/rodauth/two_factor_manage.html.erb +6 -6
  49. data/lib/generators/rodauth/templates/app/views/rodauth/unlock_account.html.erb +2 -2
  50. data/lib/generators/rodauth/templates/app/views/rodauth/unlock_account_request.html.erb +1 -1
  51. data/lib/generators/rodauth/templates/app/views/rodauth/verify_account.html.erb +3 -3
  52. data/lib/generators/rodauth/templates/app/views/rodauth/verify_account_resend.html.erb +2 -2
  53. data/lib/generators/rodauth/templates/app/views/rodauth/verify_login_change.html.erb +1 -1
  54. data/lib/generators/rodauth/templates/app/views/rodauth/webauthn_auth.html.erb +7 -7
  55. data/lib/generators/rodauth/templates/app/views/rodauth/webauthn_remove.html.erb +5 -5
  56. data/lib/generators/rodauth/templates/app/views/rodauth/webauthn_setup.html.erb +7 -7
  57. data/lib/generators/rodauth/views_generator.rb +29 -4
  58. data/lib/rodauth/rails/auth.rb +10 -13
  59. data/lib/rodauth/rails/controller_methods.rb +43 -1
  60. data/lib/rodauth/rails/feature/base.rb +8 -8
  61. data/lib/rodauth/rails/feature/callbacks.rb +6 -2
  62. data/lib/rodauth/rails/feature/internal_request.rb +50 -0
  63. data/lib/rodauth/rails/feature/render.rb +7 -0
  64. data/lib/rodauth/rails/feature.rb +2 -0
  65. data/lib/rodauth/rails/model/associations.rb +195 -0
  66. data/lib/rodauth/rails/model.rb +101 -0
  67. data/lib/rodauth/rails/tasks.rake +5 -5
  68. data/lib/rodauth/rails/version.rb +1 -1
  69. data/lib/rodauth/rails.rb +30 -13
  70. data/rodauth-rails.gemspec +4 -1
  71. metadata +50 -5
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 27d48e6bf86cf81b33f6b0282048c2fb6f16ec6602136e18de6ede5120cfd808
4
- data.tar.gz: 2f79498ff25a42131a5ead77f3d4adf05152bc85f271c8b985f0f9fa8c04b503
3
+ metadata.gz: e46466d584d7579c32e7d7e53335260dd137c04371f4b7c4680caa5c6a4e4147
4
+ data.tar.gz: c0be8bdc56f5214c885fc5ad990a0be511251cab6dbf9b0ec7aa3fbd8631d0c9
5
5
  SHA512:
6
- metadata.gz: 8a0c44b54d304d4dfb2a205d41a5ac360e483209229fa49e767f9eaa595434b291661e283110f3ee39a8fbc17a4ad2d82f90a6e4545ca4112852ee50a35aa8da
7
- data.tar.gz: 52bb16489dd97777f7ff2359be9014a2c55c7537b8d4449621eb95ef3b7f0030febcd06caa811d406db1fb24fcc884d22c7460a36a94255133ce261a2bbeb68d
6
+ metadata.gz: 8428739e888033efa811819ee8561fa3f2ae342074f6e27bbf257c18bf7029ab87380a82c75c6c08de2a0d4de49482eac74a32bc7aaf0579baf45978fe63811c
7
+ data.tar.gz: d626ea202fe8e371e6c77364a9e3c1ef34fdccff0ce7794c54b3fc748b0e1a764e92b99b6b7f06aaa8e2f2f67b155b127c0b1314d4ec7420637013136170141c
data/CHANGELOG.md CHANGED
@@ -1,3 +1,39 @@
1
+ ## 0.16.0 (2021-09-26)
2
+
3
+ * Add `#current_account` to methods defined on `ActionController::Base` (@janko)
4
+
5
+ * Add missing template for verify_login_change feature to `rodauth:views` generator (@janko)
6
+
7
+ * Add `#rodauth_response` controller method for converting rodauth responses into controller responses (@janko)
8
+
9
+ ## 0.15.0 (2021-07-29)
10
+
11
+ * Add `Rodauth::Rails::Model` mixin that defines password attribute and associations on the model (@janko)
12
+
13
+ * Add support for the new internal_request feature (@janko)
14
+
15
+ * Implement `Rodauth::Rails.rodauth` in terms of the internal_request feature (@janko)
16
+
17
+ ## 0.14.0 (2021-07-10)
18
+
19
+ * Speed up template rendering by only searching formats accepted by the request (@janko)
20
+
21
+ * Add `--name` option to `rodauth:views` generator for specifying different rodauth configuration (@janko)
22
+
23
+ * Infer correct template path from configured controller in `rodauth:views` generator (@janko)
24
+
25
+ * Raise `ArgumentError` if undefined rodauth configuration is passed to `Rodauth::Rails.app` (@janko)
26
+
27
+ * Make `#rails_controller` method on the rodauth instance public (@janko)
28
+
29
+ * Remove `--directory` option from `rodauth:views` generator (@janko)
30
+
31
+ * Remove `#features` and `#routes` writer and `#configuration` reader from `Rodauth::Rails::Auth` (@janko)
32
+
33
+ ## 0.13.0 (2021-06-10)
34
+
35
+ * Add `:query`, `:form`, `:session`, `:account`, and `:env` options to `Rodauth::Rails.rodauth` (@janko)
36
+
1
37
  ## 0.12.0 (2021-05-15)
2
38
 
3
39
  * Include total view render time in logs for Rodauth requests (@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.12"
52
+ gem "rodauth-rails", "~> 0.16"
65
53
 
66
54
  # gem "jwt", require: false # for JWT feature
67
55
  # gem "rotp", require: false # for OTP feature
@@ -154,33 +142,24 @@ end
154
142
 
155
143
  ### Current account
156
144
 
157
- To be able to fetch currently authenticated account, you can define a
158
- `#current_account` method that fetches the account id from session and
159
- retrieves the corresponding account record:
145
+ The `#current_account` method is defined in controllers and views, which
146
+ returns the model instance of the currently logged in account.
160
147
 
161
148
  ```rb
162
- # app/controllers/application_controller.rb
163
- class ApplicationController < ActionController::Base
164
- before_action :current_account, if: -> { rodauth.logged_in? }
165
-
166
- private
167
-
168
- def current_account
169
- @current_account ||= Account.find(rodauth.session_value)
170
- rescue ActiveRecord::RecordNotFound
171
- rodauth.logout
172
- rodauth.login_required
173
- end
174
- helper_method :current_account # skip if inheriting from ActionController::API
175
- end
149
+ current_account #=> #<Account id=123 email="user@example.com">
150
+ current_account.email #=> "user@example.com"
176
151
  ```
177
152
 
178
- This allows you to access the current account in controllers and views:
153
+ Pass the configuration name to retrieve accounts belonging to other Rodauth
154
+ configurations:
179
155
 
180
- ```erb
181
- <p>Authenticated as: <%= current_account.email %></p>
156
+ ```rb
157
+ current_account(:admin)
182
158
  ```
183
159
 
160
+ If the account doesn't exist in the database, the session will be cleared and
161
+ login required.
162
+
184
163
  ### Requiring authentication
185
164
 
186
165
  You'll likely want to require authentication for certain parts of your app,
@@ -290,8 +269,8 @@ $ rails generate rodauth:views
290
269
  ```
291
270
 
292
271
  This will generate views for the default set of Rodauth features into the
293
- `app/views/rodauth` directory, which will be automatically picked up by the
294
- `RodauthController`.
272
+ `app/views/rodauth` directory, provided that `RodauthController` is set for the
273
+ main configuration.
295
274
 
296
275
  You can pass a list of Rodauth features to the generator to create views for
297
276
  these features (this will not remove or overwrite any existing views):
@@ -306,12 +285,10 @@ Or you can generate views for all features:
306
285
  $ rails generate rodauth:views --all
307
286
  ```
308
287
 
309
- You can also tell the generator to create views into another directory (in this
310
- case make sure to rename the Rodauth controller accordingly):
288
+ Use `--name` to generate views for a different Rodauth configuration:
311
289
 
312
290
  ```sh
313
- # generates views into app/views/authentication
314
- $ rails generate rodauth:views --name authentication
291
+ $ rails generate rodauth:views --name admin
315
292
  ```
316
293
 
317
294
  #### Layout
@@ -404,14 +381,48 @@ end
404
381
  This configuration calls `#deliver_later`, which uses Active Job to deliver
405
382
  emails in a background job. It's generally recommended to send emails
406
383
  asynchronously for better request throughput and the ability to retry
407
- deliveries. However, if you want to send emails synchronously, modify the
408
- configuration to call `#deliver_now` instead.
384
+ deliveries. However, if you want to send emails synchronously, you can modify
385
+ the configuration to call `#deliver_now` instead.
409
386
 
410
387
  If you're using a background processing library without an Active Job adapter,
411
388
  or a 3rd-party service for sending transactional emails, this two-phase API
412
389
  might not be suitable. In this case, instead of overriding `#create_*_email`
413
390
  and `#send_email`, override the `#send_*_email` methods instead, which are
414
- required to send the email immediately.
391
+ required to send the email immediately. For example:
392
+
393
+ ```rb
394
+ # app/workers/rodauth_mailer_worker.rb
395
+ class RodauthMailerWorker
396
+ include Sidekiq::Worker
397
+
398
+ def perform(name, *args)
399
+ email = RodauthMailer.public_send(name, *args)
400
+ email.deliver_now
401
+ end
402
+ end
403
+ ```
404
+ ```rb
405
+ # app/lib/rodauth_app.rb
406
+ class RodauthApp < Rodauth::Rails::App
407
+ configure do
408
+ # ...
409
+ # use `#send_*_email` method to be able to immediately enqueue email delivery
410
+ send_reset_password_email do
411
+ enqueue_email(:reset_password, email_to, reset_password_email_link)
412
+ end
413
+ # ...
414
+ auth_class_eval do
415
+ # custom method for enqueuing email delivery using our worker
416
+ def enqueue_email(name, *args)
417
+ db.after_commit do
418
+ RodauthMailerWorker.perform_async(name, *args)
419
+ end
420
+ end
421
+ end
422
+ # ...
423
+ end
424
+ end
425
+ ```
415
426
 
416
427
  ### Migrations
417
428
 
@@ -433,6 +444,134 @@ class CreateRodauthOtpSmsCodesRecoveryCodes < ActiveRecord::Migration
433
444
  end
434
445
  ```
435
446
 
447
+ ### Model
448
+
449
+ The `Rodauth::Rails::Model` mixin can be included into the account model, which
450
+ defines a password attribute and associations for tables used by enabled
451
+ authentication features.
452
+
453
+ ```rb
454
+ class Account < ApplicationRecord
455
+ include Rodauth::Rails.model # or `Rodauth::Rails.model(:admin)`
456
+ end
457
+ ```
458
+
459
+ #### Password attribute
460
+
461
+ Regardless of whether you're storing the password hash in a column in the
462
+ accounts table, or in a separate table, the `#password` attribute can be used
463
+ to set or clear the password hash.
464
+
465
+ ```rb
466
+ account = Account.create!(email: "user@example.com", password: "secret")
467
+
468
+ # when password hash is stored in a column on the accounts table
469
+ account.password_hash #=> "$2a$12$k/Ub1I2iomi84RacqY89Hu4.M0vK7klRnRtzorDyvOkVI.hKhkNw."
470
+
471
+ # when password hash is stored in a separate table
472
+ account.password_hash #=> #<Account::PasswordHash...> (record from `account_password_hashes` table)
473
+ account.password_hash.password_hash #=> "$2a$12$k/Ub1..." (inaccessible when using database authentication functions)
474
+
475
+ account.password = nil # clears password hash
476
+ account.password_hash #=> nil
477
+ ```
478
+
479
+ Note that the password attribute doesn't come with validations, making it
480
+ unsuitable for forms. It was primarily intended to allow easily creating
481
+ accounts in development console and in tests.
482
+
483
+ #### Associations
484
+
485
+ The `Rodauth::Rails::Model` mixin defines associations for Rodauth tables
486
+ associated to the accounts table:
487
+
488
+ ```rb
489
+ account.remember_key #=> #<Account::RememberKey> (record from `account_remember_keys` table)
490
+ account.active_session_keys #=> [#<Account::ActiveSessionKey>,...] (records from `account_active_session_keys` table)
491
+ ```
492
+
493
+ You can also reference the associated models directly:
494
+
495
+ ```rb
496
+ # model referencing the `account_authentication_audit_logs` table
497
+ Account::AuthenticationAuditLog.where(message: "login").group(:account_id)
498
+ ```
499
+
500
+ The associated models define the inverse `belongs_to :account` association:
501
+
502
+ ```rb
503
+ Account::ActiveSessionKey.includes(:account).map(&:account)
504
+ ```
505
+
506
+ Here is an example of using associations to create a method that returns
507
+ whether the account has multifactor authentication enabled:
508
+
509
+ ```rb
510
+ class Account < ApplicationRecord
511
+ include Rodauth::Rails.model
512
+
513
+ def mfa_enabled?
514
+ otp_key || (sms_code && sms_code.num_failures.nil?) || recovery_codes.any?
515
+ end
516
+ end
517
+ ```
518
+
519
+ Here is another example of creating a query scope that selects accounts with
520
+ multifactor authentication enabled:
521
+
522
+ ```rb
523
+ class Account < ApplicationRecord
524
+ include Rodauth::Rails.model
525
+
526
+ scope :otp_setup, -> { where(otp_key: OtpKey.all) }
527
+ scope :sms_codes_setup, -> { where(sms_code: SmsCode.where(num_failures: nil)) }
528
+ scope :recovery_codes_setup, -> { where(recovery_codes: RecoveryCode.all) }
529
+ scope :mfa_enabled, -> { merge(otp_setup.or(sms_codes_setup).or(recovery_codes_setup)) }
530
+ end
531
+ ```
532
+
533
+ Below is a list of all associations defined depending on the features loaded:
534
+
535
+ | Feature | Association | Type | Model | Table (default) |
536
+ | :------ | :---------- | :--- | :---- | :---- |
537
+ | account_expiration | `:activity_time` | `has_one` | `ActivityTime` | `account_activity_times` |
538
+ | active_sessions | `:active_session_keys` | `has_many` | `ActiveSessionKey` | `account_active_session_keys` |
539
+ | audit_logging | `:authentication_audit_logs` | `has_many` | `AuthenticationAuditLog` | `account_authentication_audit_logs` |
540
+ | disallow_password_reuse | `:previous_password_hashes` | `has_many` | `PreviousPasswordHash` | `account_previous_password_hashes` |
541
+ | email_auth | `:email_auth_key` | `has_one` | `EmailAuthKey` | `account_email_auth_keys` |
542
+ | jwt_refresh | `:jwt_refresh_keys` | `has_many` | `JwtRefreshKey` | `account_jwt_refresh_keys` |
543
+ | lockout | `:lockout` | `has_one` | `Lockout` | `account_lockouts` |
544
+ | lockout | `:login_failure` | `has_one` | `LoginFailure` | `account_login_failures` |
545
+ | otp | `:otp_key` | `has_one` | `OtpKey` | `account_otp_keys` |
546
+ | password_expiration | `:password_change_time` | `has_one` | `PasswordChangeTime` | `account_password_change_times` |
547
+ | recovery_codes | `:recovery_codes` | `has_many` | `RecoveryCode` | `account_recovery_codes` |
548
+ | remember | `:remember_key` | `has_one` | `RememberKey` | `account_remember_keys` |
549
+ | reset_password | `:password_reset_key` | `has_one` | `PasswordResetKey` | `account_password_reset_keys` |
550
+ | single_session | `:session_key` | `has_one` | `SessionKey` | `account_session_keys` |
551
+ | sms_codes | `:sms_code` | `has_one` | `SmsCode` | `account_sms_codes` |
552
+ | verify_account | `:verification_key` | `has_one` | `VerificationKey` | `account_verification_keys` |
553
+ | verify_login_change | `:login_change_key` | `has_one` | `LoginChangeKey` | `account_login_change_keys` |
554
+ | webauthn | `:webauthn_keys` | `has_many` | `WebauthnKey` | `account_webauthn_keys` |
555
+ | webauthn | `:webauthn_user_id` | `has_one` | `WebauthnUserId` | `account_webauthn_user_ids` |
556
+
557
+ By default, all associations except for audit logs have `dependent: :destroy`
558
+ set, to allow for easy deletion of account records in the console. You can use
559
+ `:association_options` to modify global or per-association options:
560
+
561
+ ```rb
562
+ # don't auto-delete associations when account model is deleted
563
+ Rodauth::Rails.model(association_options: { dependent: nil })
564
+
565
+ # require authentication audit logs to be eager loaded before retrieval
566
+ Rodauth::Rails.model(association_options: -> (name) {
567
+ { strict_loading: true } if name == :authentication_audit_logs
568
+ })
569
+ ```
570
+
571
+ Note that some Rodauth tables use composite primary keys, which Active Record
572
+ doesn't support out of the box. For associations to work properly, you might
573
+ need to add the [composite_primary_keys] gem to your Gemfile.
574
+
436
575
  ### Multiple configurations
437
576
 
438
577
  If you need to handle multiple types of accounts that require different
@@ -452,10 +591,6 @@ class RodauthApp < Rodauth::Rails::App
452
591
  prefix "/admin"
453
592
  session_key_prefix "admin_"
454
593
  remember_cookie_key "_admin_remember" # if using remember feature
455
-
456
- # if you want separate tables
457
- accounts_table :admin_accounts
458
- password_hash_table :admin_account_password_hashes
459
594
  # ...
460
595
  end
461
596
 
@@ -464,7 +599,7 @@ class RodauthApp < Rodauth::Rails::App
464
599
 
465
600
  r.on "admin" do
466
601
  r.rodauth(:admin)
467
- r.pass # allow the Rails app to handle other "/admin/*" requests
602
+ break # allow routing of other /admin/* requests to continue to Rails
468
603
  end
469
604
 
470
605
  # ...
@@ -478,6 +613,50 @@ Then in your application you can reference the secondary Rodauth instance:
478
613
  rodauth(:admin).login_path #=> "/admin/login"
479
614
  ```
480
615
 
616
+ You'll likely want to save the information of which account belongs to which
617
+ configuration to the database. One way would be to have a separate table that
618
+ stores account types:
619
+
620
+ ```sh
621
+ $ rails generate migration create_account_types
622
+ ```
623
+ ```rb
624
+ # db/migrate/*_create_account_types.rb
625
+ class CreateAccountTypes < ActiveRecord::Migration
626
+ def change
627
+ create_table :account_types do |t|
628
+ t.references :account, foreign_key: { on_delete: :cascade }, null: false
629
+ t.string :type, null: false
630
+ end
631
+ end
632
+ end
633
+ ```
634
+ ```sh
635
+ $ rails db:migrate
636
+ ```
637
+
638
+ Then an entry would be inserted after account creation, and optionally whenever
639
+ Rodauth retrieves accounts you could filter only those belonging to the current
640
+ configuration:
641
+
642
+ ```rb
643
+ # app/lib/rodauth_app.rb
644
+ class RodauthApp < Rodauth::Rails::App
645
+ configure(:admin) do
646
+ # ...
647
+ after_create_account do
648
+ db[:account_types].insert(account_id: account_id, type: "admin")
649
+ end
650
+ auth_class_eval do
651
+ def account_ds(*)
652
+ super.join(:account_types, account_id: :id).where(type: "admin")
653
+ end
654
+ end
655
+ # ...
656
+ end
657
+ end
658
+ ```
659
+
481
660
  #### Named auth classes
482
661
 
483
662
  A `configure` block inside `Rodauth::Rails::App` will internally create an
@@ -596,24 +775,55 @@ class RodauthApp < Rodauth::Rails::App
596
775
  end
597
776
  ```
598
777
 
599
- ### Rodauth instance
778
+ ### Outside of a request
600
779
 
601
- In some cases you might need to use Rodauth more programmatically, and perform
602
- Rodauth operations outside of the request context. rodauth-rails gives you the
603
- ability to retrieve the Rodauth instance:
780
+ In some cases you might need to use Rodauth more programmatically. If you would
781
+ like to perform Rodauth operations outside of request context, Rodauth ships
782
+ with the [internal_request] feature just for that. The rodauth-rails gem
783
+ additionally updates the internal rack env hash with your
784
+ `config.action_mailer.default_url_options`, which is used for generating URLs.
604
785
 
786
+ If you need to access Rodauth methods not exposed as internal requests, you can
787
+ use `Rodauth::Rails.rodauth` to retrieve the Rodauth instance used by the
788
+ internal_request feature:
789
+
790
+ ```rb
791
+ # app/lib/rodauth_app.rb
792
+ class RodauthApp < Rodauth::Rails::App
793
+ configure do
794
+ enable :internal_request # this is required
795
+ end
796
+ end
797
+ ```
605
798
  ```rb
606
- rodauth = Rodauth::Rails.rodauth # or Rodauth::Rails.rodauth(:admin)
799
+ account = Account.find_by!(email: "user@example.com")
800
+ rodauth = Rodauth::Rails.rodauth(account: account)
607
801
 
608
- rodauth.login_url #=> "https://example.com/login"
609
- rodauth.account_from_login("user@example.com") # loads user by email
610
- rodauth.password_match?("secret") #=> true
611
- rodauth.setup_account_verification
612
- rodauth.close_account
802
+ rodauth.compute_hmac("token") #=> "TpEJTKfKwqYvIDKWsuZhkhKlhaBXtR1aodskBAflD8U"
803
+ rodauth.open_account? #=> true
804
+ rodauth.two_factor_authentication_setup? #=> true
805
+ rodauth.password_meets_requirements?("foo") #=> false
806
+ rodauth.locked_out? #=> false
613
807
  ```
614
808
 
615
- This Rodauth instance will be initialized with basic Rack env that allows it
616
- to generate URLs, using `config.action_mailer.default_url_options` options.
809
+ In addition to the `:account` option, the `Rodauth::Rails.rodauth`
810
+ method accepts any options supported by the internal_request feature.
811
+
812
+ ```rb
813
+ Rodauth::Rails.rodauth(
814
+ env: { "HTTP_USER_AGENT" => "programmatic" },
815
+ session: { two_factor_auth_setup: true },
816
+ params: { "param" => "value" },
817
+ # ...
818
+ )
819
+ ```
820
+
821
+ Secondary Rodauth configurations are specified by passing the configuration
822
+ name:
823
+
824
+ ```rb
825
+ Rodauth::Rails.rodauth(:admin)
826
+ ```
617
827
 
618
828
  ## How it works
619
829
 
@@ -751,21 +961,23 @@ class RodauthApp < Rodauth::Rails::App
751
961
  end
752
962
  ```
753
963
 
754
- If you need Cross-Origin Resource Sharing and/or JWT refresh tokens, enable the
755
- corresponding Rodauth features and create the necessary tables:
964
+ The JWT token will be returned after each request to Rodauth routes. To also
965
+ return the JWT token on requests to your app's routes, you can add the
966
+ following code to your base controller:
756
967
 
757
- ```sh
758
- $ rails generate rodauth:migration jwt_refresh
759
- $ rails db:migrate
760
- ```
761
968
  ```rb
762
- # app/lib/rodauth_app.rb
763
- class RodauthApp < Rodauth::Rails::App
764
- configure do
765
- # ...
766
- enable :jwt, :jwt_cors, :jwt_refresh
767
- # ...
969
+ class ApplicationController < ActionController::Base
970
+ # ...
971
+ after_action :set_jwt_token
972
+
973
+ private
974
+
975
+ def set_jwt_token
976
+ if rodauth.use_jwt? && rodauth.valid_jwt?
977
+ response.headers["Authorization"] = rodauth.session_jwt
978
+ end
768
979
  end
980
+ # ...
769
981
  end
770
982
  ```
771
983
 
@@ -870,9 +1082,13 @@ class RodauthController < ApplicationController
870
1082
  account.identities.create!(provider: auth["provider"], uid: auth["uid"], info: auth["info"])
871
1083
  end
872
1084
 
873
- # login with Rodauth
1085
+ # load the account into the rodauth instance
874
1086
  rodauth.account_from_login(account.email)
875
- rodauth.login("omniauth")
1087
+
1088
+ rodauth_response do # ensures any `after_action` callbacks get called
1089
+ # sign in the loaded account
1090
+ rodauth.login("omniauth")
1091
+ end
876
1092
  end
877
1093
  end
878
1094
  ```
@@ -1073,29 +1289,6 @@ Rails.application.configure do |config|
1073
1289
  end
1074
1290
  ```
1075
1291
 
1076
- If you need to create an account record with a password directly, you can do it
1077
- as follows:
1078
-
1079
- ```rb
1080
- # app/models/account.rb
1081
- class Account < ApplicationRecord
1082
- has_one :password_hash, foreign_key: :id
1083
- end
1084
- ```
1085
- ```rb
1086
- # app/models/account/password_hash.rb
1087
- class Account::PasswordHash < ApplicationRecord
1088
- belongs_to :account, foreign_key: :id
1089
- end
1090
- ```
1091
- ```rb
1092
- require "bcrypt"
1093
-
1094
- account = Account.create!(email: "user@example.com", status: "verified")
1095
- password_hash = BCrypt::Password.create("secret", cost: BCrypt::Engine::MIN_COST)
1096
- account.create_password_hash!(id: account.id, password_hash: password_hash)
1097
- ```
1098
-
1099
1292
  ## Rodauth defaults
1100
1293
 
1101
1294
  rodauth-rails changes some of the default Rodauth settings for easier setup:
@@ -1176,6 +1369,18 @@ configure do
1176
1369
  end
1177
1370
  ```
1178
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
+
1179
1384
  ## License
1180
1385
 
1181
1386
  The gem is available as open source under the terms of the [MIT
@@ -1217,3 +1422,4 @@ conduct](https://github.com/janko/rodauth-rails/blob/master/CODE_OF_CONDUCT.md).
1217
1422
  [single_session]: http://rodauth.jeremyevans.net/rdoc/files/doc/single_session_rdoc.html
1218
1423
  [account_expiration]: http://rodauth.jeremyevans.net/rdoc/files/doc/account_expiration_rdoc.html
1219
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
@@ -154,11 +154,15 @@ class RodauthApp < Rodauth::Rails::App
154
154
  <% end -%>
155
155
  end
156
156
 
157
- # ==> Multiple configurations
157
+ # ==> Secondary configurations
158
158
  # configure(:admin) do
159
- # enable :http_basic_auth # enable different set of features
159
+ # # ... enable features ...
160
160
  # prefix "/admin"
161
161
  # session_key_prefix "admin_"
162
+ # # remember_cookie_key "_admin_remember" # if using remember feature
163
+ #
164
+ # # search views in `app/views/admin/rodauth` directory
165
+ # rails_controller { Admin::RodauthController }
162
166
  # end
163
167
 
164
168
  route do |r|
@@ -180,7 +184,7 @@ class RodauthApp < Rodauth::Rails::App
180
184
  # rodauth.require_authentication
181
185
  # end
182
186
 
183
- # ==> Multiple configurations
187
+ # ==> Secondary configurations
184
188
  # r.on "admin" do
185
189
  # r.rodauth(:admin)
186
190
  #
@@ -188,7 +192,7 @@ class RodauthApp < Rodauth::Rails::App
188
192
  # rodauth(:admin).require_http_basic_auth
189
193
  # end
190
194
  #
191
- # r.pass # allow the Rails app to handle other "/admin/*" requests
195
+ # break # allow the Rails app to handle other "/admin/*" requests
192
196
  # end
193
197
  end
194
198
  end
@@ -1,2 +1,3 @@
1
1
  class Account < ApplicationRecord
2
+ include Rodauth::Rails.model
2
3
  end
@@ -1,4 +1,4 @@
1
- <%%= form_tag rodauth.email_auth_request_path, method: :post do %>
1
+ <%%= form_tag <%= rodauth %>.email_auth_request_path, method: :post do %>
2
2
  <%%= render "login_hidden_field" %>
3
3
  <%%= render "submit", value: "Send Login Link Via Email" %>
4
4
  <%% end %>
@@ -4,7 +4,7 @@
4
4
  autocomplete: local_assigns[:autocomplete],
5
5
  inputmode: local_assigns[:inputmode],
6
6
  required: local_assigns[:required] != false,
7
- class: "#{local_assigns[:class] || "form-control"} #{"is-invalid" if rodauth.field_error(name)}",
8
- aria: ({ invalid: "true", describedby: "#{name}_error_message" } if rodauth.field_error(name)) %>
7
+ class: "#{local_assigns[:class] || "form-control"} #{"is-invalid" if <%= rodauth %>.field_error(name)}",
8
+ aria: ({ invalid: "true", describedby: "#{name}_error_message" } if <%= rodauth %>.field_error(name)) %>
9
9
 
10
10
  <%%= render "field_error", name: name unless local_assigns[:skip_error_message] %>
@@ -1,3 +1,3 @@
1
- <%% if rodauth.field_error(name) %>
2
- <div class="invalid-feedback" id="<%%= name %>_error_message"><%%= rodauth.field_error(name) %></div>
1
+ <%% if <%= rodauth %>.field_error(name) %>
2
+ <div class="invalid-feedback" id="<%%= name %>_error_message"><%%= <%= rodauth %>.field_error(name) %></div>
3
3
  <%% end %>
@@ -1,6 +1,6 @@
1
1
  <div class="form-group mb-3">
2
2
  <div class="form-check">
3
- <%%= check_box_tag rodauth.global_logout_param, "t", false, id: "global-logout", class: "form-check-input" %>
3
+ <%%= check_box_tag <%= rodauth %>.global_logout_param, "t", false, id: "global-logout", class: "form-check-input" %>
4
4
  <%%= label_tag "global-logout", "Logout all Logged In Sessons?", class: "form-check-label" %>
5
5
  </div>
6
6
  </div>
@@ -1,4 +1,4 @@
1
1
  <div class="form-group mb-3">
2
2
  <%%= label_tag "login-confirm", "Confirm Login", class: "form-label" %>
3
- <%%= render "field", name: rodauth.login_confirm_param, id: "login-confirm", type: :email, autocomplete: "email" %>
3
+ <%%= render "field", name: <%= rodauth %>.login_confirm_param, id: "login-confirm", type: :email, autocomplete: "email" %>
4
4
  </div>
@@ -1,4 +1,4 @@
1
1
  <div class="form-group mb-3">
2
2
  <%%= label_tag "login", "Login", class: "form-label" %>
3
- <%%= email_field_tag rodauth.login_param, params[rodauth.login_param], id: "login", readonly: true, class: "form-control-plaintext" %>
3
+ <%%= email_field_tag <%= rodauth %>.login_param, params[<%= rodauth %>.login_param], id: "login", readonly: true, class: "form-control-plaintext" %>
4
4
  </div>