rodauth-rails 0.13.0 → 0.17.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (71) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +40 -0
  3. data/README.md +333 -90
  4. data/lib/generators/rodauth/templates/app/lib/rodauth_app.rb +9 -5
  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 +42 -1
  60. data/lib/rodauth/rails/feature/base.rb +17 -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 +27 -28
  70. data/rodauth-rails.gemspec +4 -1
  71. metadata +49 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cc8ee44d094627dcacd9d9b7f5da1eb165cff1af209f079b667e0f04e9540b30
4
- data.tar.gz: f179e4eaea99d04ff6ff71c6357cdf75a19991645c9904ab6373c03b5dcd1a16
3
+ metadata.gz: 1539e5f70a8cefa3c40e06b5b177152e4772f099deb11a077f07f59529622a62
4
+ data.tar.gz: 67c9a6829f8a9c45708cb1ab0781a2eebe1998d5f31f66b26d5c7f58cb37cdf8
5
5
  SHA512:
6
- metadata.gz: 78c28c13751abb439179813948bf665cd040444171998e42ecdb4cb42f698097731f4c073b7595d083ba5825a9989940deee052771fb5f76f93bd333e94af500
7
- data.tar.gz: eb3a04ae6333dc471fd7fbdb264527a359893fb100d7833bab3545f7d91e213bfc8a2daa562ffc22f531073f39f4bee3de893bffd87201b3e57d1dce99c97320
6
+ metadata.gz: bf1f132504de2266dc4ef7f71ffdd630e348119f6681f84288aeb6ba24481336948c78183d4fa7e90100dedc85e04c4bb98f915de3ecf156630d523d91d74c00
7
+ data.tar.gz: e1858507c3ee9a2855e04fa67957859f41347adbf448793b8cebe263a0bd95517ef913b4132470a31609be9741ff73e4769309df5924f19b0db0503a1a25fa2a
data/CHANGELOG.md CHANGED
@@ -1,3 +1,43 @@
1
+ ## 0.17.0 (2021-10-05)
2
+
3
+ * Set `delete_account_on_close?` to `true` in generated `rodauth_app.rb` (@janko)
4
+
5
+ * Change default `:dependent` option for associations to `:delete`/`:delete_all` (@janko)
6
+
7
+ * Add `rails_account_model` configuration method for when the account model cannot be inferred (@janko)
8
+
9
+ ## 0.16.0 (2021-09-26)
10
+
11
+ * Add `#current_account` to methods defined on `ActionController::Base` (@janko)
12
+
13
+ * Add missing template for verify_login_change feature to `rodauth:views` generator (@janko)
14
+
15
+ * Add `#rodauth_response` controller method for converting rodauth responses into controller responses (@janko)
16
+
17
+ ## 0.15.0 (2021-07-29)
18
+
19
+ * Add `Rodauth::Rails::Model` mixin that defines password attribute and associations on the model (@janko)
20
+
21
+ * Add support for the new internal_request feature (@janko)
22
+
23
+ * Implement `Rodauth::Rails.rodauth` in terms of the internal_request feature (@janko)
24
+
25
+ ## 0.14.0 (2021-07-10)
26
+
27
+ * Speed up template rendering by only searching formats accepted by the request (@janko)
28
+
29
+ * Add `--name` option to `rodauth:views` generator for specifying different rodauth configuration (@janko)
30
+
31
+ * Infer correct template path from configured controller in `rodauth:views` generator (@janko)
32
+
33
+ * Raise `ArgumentError` if undefined rodauth configuration is passed to `Rodauth::Rails.app` (@janko)
34
+
35
+ * Make `#rails_controller` method on the rodauth instance public (@janko)
36
+
37
+ * Remove `--directory` option from `rodauth:views` generator (@janko)
38
+
39
+ * Remove `#features` and `#routes` writer and `#configuration` reader from `Rodauth::Rails::Auth` (@janko)
40
+
1
41
  ## 0.13.0 (2021-06-10)
2
42
 
3
43
  * Add `:query`, `:form`, `:session`, `:account`, and `:env` options to `Rodauth::Rails.rodauth` (@janko)
data/README.md CHANGED
@@ -49,7 +49,7 @@ For instructions on upgrading from previous rodauth-rails versions, see
49
49
  Add the gem to your Gemfile:
50
50
 
51
51
  ```rb
52
- gem "rodauth-rails", "~> 0.13"
52
+ gem "rodauth-rails", "~> 0.17"
53
53
 
54
54
  # gem "jwt", require: false # for JWT feature
55
55
  # gem "rotp", require: false # for OTP feature
@@ -142,31 +142,36 @@ end
142
142
 
143
143
  ### Current account
144
144
 
145
- To be able to fetch currently authenticated account, you can define a
146
- `#current_account` method that fetches the account id from session and
147
- 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.
148
147
 
149
148
  ```rb
150
- # app/controllers/application_controller.rb
151
- class ApplicationController < ActionController::Base
152
- before_action :current_account, if: -> { rodauth.logged_in? }
149
+ current_account #=> #<Account id=123 email="user@example.com">
150
+ current_account.email #=> "user@example.com"
151
+ ```
153
152
 
154
- private
153
+ If the account doesn't exist in the database, the session will be cleared and
154
+ login required.
155
155
 
156
- def current_account
157
- @current_account ||= Account.find(rodauth.session_value)
158
- rescue ActiveRecord::RecordNotFound
159
- rodauth.logout
160
- rodauth.login_required
161
- end
162
- helper_method :current_account # skip if inheriting from ActionController::API
163
- end
156
+ Pass the configuration name to retrieve accounts belonging to other Rodauth
157
+ configurations:
158
+
159
+ ```rb
160
+ current_account(:admin)
164
161
  ```
165
162
 
166
- This allows you to access the current account in controllers and views:
163
+ The `#current_account` method will try to infer the account model class from
164
+ the configured table name. If that fails, you can set the account model
165
+ manually:
167
166
 
168
- ```erb
169
- <p>Authenticated as: <%= current_account.email %></p>
167
+ ```rb
168
+ # app/lib/rodauth_app.rb
169
+ class RodauthApp < Rodauth::Rails::App
170
+ configure do
171
+ # ...
172
+ rails_account_model Authentication::Account # custom model name
173
+ end
174
+ end
170
175
  ```
171
176
 
172
177
  ### Requiring authentication
@@ -278,8 +283,8 @@ $ rails generate rodauth:views
278
283
  ```
279
284
 
280
285
  This will generate views for the default set of Rodauth features into the
281
- `app/views/rodauth` directory, which will be automatically picked up by the
282
- `RodauthController`.
286
+ `app/views/rodauth` directory, provided that `RodauthController` is set for the
287
+ main configuration.
283
288
 
284
289
  You can pass a list of Rodauth features to the generator to create views for
285
290
  these features (this will not remove or overwrite any existing views):
@@ -294,12 +299,10 @@ Or you can generate views for all features:
294
299
  $ rails generate rodauth:views --all
295
300
  ```
296
301
 
297
- You can also tell the generator to create views into another directory (in this
298
- case make sure to rename the Rodauth controller accordingly):
302
+ Use `--name` to generate views for a different Rodauth configuration:
299
303
 
300
304
  ```sh
301
- # generates views into app/views/authentication
302
- $ rails generate rodauth:views --name authentication
305
+ $ rails generate rodauth:views --name admin
303
306
  ```
304
307
 
305
308
  #### Layout
@@ -392,14 +395,48 @@ end
392
395
  This configuration calls `#deliver_later`, which uses Active Job to deliver
393
396
  emails in a background job. It's generally recommended to send emails
394
397
  asynchronously for better request throughput and the ability to retry
395
- deliveries. However, if you want to send emails synchronously, modify the
396
- configuration to call `#deliver_now` instead.
398
+ deliveries. However, if you want to send emails synchronously, you can modify
399
+ the configuration to call `#deliver_now` instead.
397
400
 
398
401
  If you're using a background processing library without an Active Job adapter,
399
402
  or a 3rd-party service for sending transactional emails, this two-phase API
400
403
  might not be suitable. In this case, instead of overriding `#create_*_email`
401
404
  and `#send_email`, override the `#send_*_email` methods instead, which are
402
- required to send the email immediately.
405
+ required to send the email immediately. For example:
406
+
407
+ ```rb
408
+ # app/workers/rodauth_mailer_worker.rb
409
+ class RodauthMailerWorker
410
+ include Sidekiq::Worker
411
+
412
+ def perform(name, *args)
413
+ email = RodauthMailer.public_send(name, *args)
414
+ email.deliver_now
415
+ end
416
+ end
417
+ ```
418
+ ```rb
419
+ # app/lib/rodauth_app.rb
420
+ class RodauthApp < Rodauth::Rails::App
421
+ configure do
422
+ # ...
423
+ # use `#send_*_email` method to be able to immediately enqueue email delivery
424
+ send_reset_password_email do
425
+ enqueue_email(:reset_password, email_to, reset_password_email_link)
426
+ end
427
+ # ...
428
+ auth_class_eval do
429
+ # custom method for enqueuing email delivery using our worker
430
+ def enqueue_email(name, *args)
431
+ db.after_commit do
432
+ RodauthMailerWorker.perform_async(name, *args)
433
+ end
434
+ end
435
+ end
436
+ # ...
437
+ end
438
+ end
439
+ ```
403
440
 
404
441
  ### Migrations
405
442
 
@@ -421,6 +458,134 @@ class CreateRodauthOtpSmsCodesRecoveryCodes < ActiveRecord::Migration
421
458
  end
422
459
  ```
423
460
 
461
+ ### Model
462
+
463
+ The `Rodauth::Rails::Model` mixin can be included into the account model, which
464
+ defines a password attribute and associations for tables used by enabled
465
+ authentication features.
466
+
467
+ ```rb
468
+ class Account < ApplicationRecord
469
+ include Rodauth::Rails.model # or `Rodauth::Rails.model(:admin)`
470
+ end
471
+ ```
472
+
473
+ #### Password attribute
474
+
475
+ Regardless of whether you're storing the password hash in a column in the
476
+ accounts table, or in a separate table, the `#password` attribute can be used
477
+ to set or clear the password hash.
478
+
479
+ ```rb
480
+ account = Account.create!(email: "user@example.com", password: "secret")
481
+
482
+ # when password hash is stored in a column on the accounts table
483
+ account.password_hash #=> "$2a$12$k/Ub1I2iomi84RacqY89Hu4.M0vK7klRnRtzorDyvOkVI.hKhkNw."
484
+
485
+ # when password hash is stored in a separate table
486
+ account.password_hash #=> #<Account::PasswordHash...> (record from `account_password_hashes` table)
487
+ account.password_hash.password_hash #=> "$2a$12$k/Ub1..." (inaccessible when using database authentication functions)
488
+
489
+ account.password = nil # clears password hash
490
+ account.password_hash #=> nil
491
+ ```
492
+
493
+ Note that the password attribute doesn't come with validations, making it
494
+ unsuitable for forms. It was primarily intended to allow easily creating
495
+ accounts in development console and in tests.
496
+
497
+ #### Associations
498
+
499
+ The `Rodauth::Rails::Model` mixin defines associations for Rodauth tables
500
+ associated to the accounts table:
501
+
502
+ ```rb
503
+ account.remember_key #=> #<Account::RememberKey> (record from `account_remember_keys` table)
504
+ account.active_session_keys #=> [#<Account::ActiveSessionKey>,...] (records from `account_active_session_keys` table)
505
+ ```
506
+
507
+ You can also reference the associated models directly:
508
+
509
+ ```rb
510
+ # model referencing the `account_authentication_audit_logs` table
511
+ Account::AuthenticationAuditLog.where(message: "login").group(:account_id)
512
+ ```
513
+
514
+ The associated models define the inverse `belongs_to :account` association:
515
+
516
+ ```rb
517
+ Account::ActiveSessionKey.includes(:account).map(&:account)
518
+ ```
519
+
520
+ Here is an example of using associations to create a method that returns
521
+ whether the account has multifactor authentication enabled:
522
+
523
+ ```rb
524
+ class Account < ApplicationRecord
525
+ include Rodauth::Rails.model
526
+
527
+ def mfa_enabled?
528
+ otp_key || (sms_code && sms_code.num_failures.nil?) || recovery_codes.any?
529
+ end
530
+ end
531
+ ```
532
+
533
+ Here is another example of creating a query scope that selects accounts with
534
+ multifactor authentication enabled:
535
+
536
+ ```rb
537
+ class Account < ApplicationRecord
538
+ include Rodauth::Rails.model
539
+
540
+ scope :otp_setup, -> { where(otp_key: OtpKey.all) }
541
+ scope :sms_codes_setup, -> { where(sms_code: SmsCode.where(num_failures: nil)) }
542
+ scope :recovery_codes_setup, -> { where(recovery_codes: RecoveryCode.all) }
543
+ scope :mfa_enabled, -> { merge(otp_setup.or(sms_codes_setup).or(recovery_codes_setup)) }
544
+ end
545
+ ```
546
+
547
+ Below is a list of all associations defined depending on the features loaded:
548
+
549
+ | Feature | Association | Type | Model | Table (default) |
550
+ | :------ | :---------- | :--- | :---- | :---- |
551
+ | account_expiration | `:activity_time` | `has_one` | `ActivityTime` | `account_activity_times` |
552
+ | active_sessions | `:active_session_keys` | `has_many` | `ActiveSessionKey` | `account_active_session_keys` |
553
+ | audit_logging | `:authentication_audit_logs` | `has_many` | `AuthenticationAuditLog` | `account_authentication_audit_logs` |
554
+ | disallow_password_reuse | `:previous_password_hashes` | `has_many` | `PreviousPasswordHash` | `account_previous_password_hashes` |
555
+ | email_auth | `:email_auth_key` | `has_one` | `EmailAuthKey` | `account_email_auth_keys` |
556
+ | jwt_refresh | `:jwt_refresh_keys` | `has_many` | `JwtRefreshKey` | `account_jwt_refresh_keys` |
557
+ | lockout | `:lockout` | `has_one` | `Lockout` | `account_lockouts` |
558
+ | lockout | `:login_failure` | `has_one` | `LoginFailure` | `account_login_failures` |
559
+ | otp | `:otp_key` | `has_one` | `OtpKey` | `account_otp_keys` |
560
+ | password_expiration | `:password_change_time` | `has_one` | `PasswordChangeTime` | `account_password_change_times` |
561
+ | recovery_codes | `:recovery_codes` | `has_many` | `RecoveryCode` | `account_recovery_codes` |
562
+ | remember | `:remember_key` | `has_one` | `RememberKey` | `account_remember_keys` |
563
+ | reset_password | `:password_reset_key` | `has_one` | `PasswordResetKey` | `account_password_reset_keys` |
564
+ | single_session | `:session_key` | `has_one` | `SessionKey` | `account_session_keys` |
565
+ | sms_codes | `:sms_code` | `has_one` | `SmsCode` | `account_sms_codes` |
566
+ | verify_account | `:verification_key` | `has_one` | `VerificationKey` | `account_verification_keys` |
567
+ | verify_login_change | `:login_change_key` | `has_one` | `LoginChangeKey` | `account_login_change_keys` |
568
+ | webauthn | `:webauthn_keys` | `has_many` | `WebauthnKey` | `account_webauthn_keys` |
569
+ | webauthn | `:webauthn_user_id` | `has_one` | `WebauthnUserId` | `account_webauthn_user_ids` |
570
+
571
+ By default, all associations except for audit logs have `dependent: :destroy`
572
+ set, to allow for easy deletion of account records in the console. You can use
573
+ `:association_options` to modify global or per-association options:
574
+
575
+ ```rb
576
+ # don't auto-delete associations when account model is deleted
577
+ Rodauth::Rails.model(association_options: { dependent: nil })
578
+
579
+ # require authentication audit logs to be eager loaded before retrieval
580
+ Rodauth::Rails.model(association_options: -> (name) {
581
+ { strict_loading: true } if name == :authentication_audit_logs
582
+ })
583
+ ```
584
+
585
+ Note that some Rodauth tables use composite primary keys, which Active Record
586
+ doesn't support out of the box. For associations to work properly, you might
587
+ need to add the [composite_primary_keys] gem to your Gemfile.
588
+
424
589
  ### Multiple configurations
425
590
 
426
591
  If you need to handle multiple types of accounts that require different
@@ -440,10 +605,6 @@ class RodauthApp < Rodauth::Rails::App
440
605
  prefix "/admin"
441
606
  session_key_prefix "admin_"
442
607
  remember_cookie_key "_admin_remember" # if using remember feature
443
-
444
- # if you want separate tables
445
- accounts_table :admin_accounts
446
- password_hash_table :admin_account_password_hashes
447
608
  # ...
448
609
  end
449
610
 
@@ -466,6 +627,50 @@ Then in your application you can reference the secondary Rodauth instance:
466
627
  rodauth(:admin).login_path #=> "/admin/login"
467
628
  ```
468
629
 
630
+ You'll likely want to save the information of which account belongs to which
631
+ configuration to the database. One way would be to have a separate table that
632
+ stores account types:
633
+
634
+ ```sh
635
+ $ rails generate migration create_account_types
636
+ ```
637
+ ```rb
638
+ # db/migrate/*_create_account_types.rb
639
+ class CreateAccountTypes < ActiveRecord::Migration
640
+ def change
641
+ create_table :account_types do |t|
642
+ t.references :account, foreign_key: { on_delete: :cascade }, null: false
643
+ t.string :type, null: false
644
+ end
645
+ end
646
+ end
647
+ ```
648
+ ```sh
649
+ $ rails db:migrate
650
+ ```
651
+
652
+ Then an entry would be inserted after account creation, and optionally whenever
653
+ Rodauth retrieves accounts you could filter only those belonging to the current
654
+ configuration:
655
+
656
+ ```rb
657
+ # app/lib/rodauth_app.rb
658
+ class RodauthApp < Rodauth::Rails::App
659
+ configure(:admin) do
660
+ # ...
661
+ after_create_account do
662
+ db[:account_types].insert(account_id: account_id, type: "admin")
663
+ end
664
+ auth_class_eval do
665
+ def account_ds(*)
666
+ super.join(:account_types, account_id: :id).where(type: "admin")
667
+ end
668
+ end
669
+ # ...
670
+ end
671
+ end
672
+ ```
673
+
469
674
  #### Named auth classes
470
675
 
471
676
  A `configure` block inside `Rodauth::Rails::App` will internally create an
@@ -584,37 +789,88 @@ class RodauthApp < Rodauth::Rails::App
584
789
  end
585
790
  ```
586
791
 
587
- ### Rodauth instance
792
+ ### Outside of a request
588
793
 
589
- In some cases you might need to use Rodauth more programmatically, and perform
590
- Rodauth operations outside of the request context. rodauth-rails gives you a
591
- helper method for building a Rodauth instance:
794
+ In some cases you might need to use Rodauth more programmatically. If you want
795
+ to perform authentication operations outside of request context, Rodauth ships
796
+ with the [internal_request] feature just for that.
592
797
 
593
798
  ```rb
594
- rodauth = Rodauth::Rails.rodauth # or Rodauth::Rails.rodauth(:admin)
799
+ # app/lib/rodauth_app.rb
800
+ class RodauthApp < Rodauth::Rails::App
801
+ configure do
802
+ enable :internal_request
803
+ end
804
+ end
805
+ ```
806
+ ```rb
807
+ # main configuration
808
+ RodauthApp.rodauth.create_account(login: "user@example.com", password: "secret")
809
+ RodauthApp.rodauth.verify_account(account_login: "user@example.com")
595
810
 
596
- rodauth.login_url #=> "https://example.com/login"
597
- rodauth.account_from_login("user@example.com") # loads user by email
598
- rodauth.password_match?("secret") #=> true
599
- rodauth.setup_account_verification
600
- rodauth.close_account
811
+ # secondary configuration
812
+ RodauthApp.rodauth(:admin).close_account(account_login: "admin@example.com")
601
813
  ```
602
814
 
603
- The base URL is taken from Action Mailer's `default_url_options` setting if
604
- configured. The `Rodauth::Rails.rodauth` method accepts additional keyword
605
- arguments:
815
+ The rodauth-rails gem additionally updates the internal rack env hash with your
816
+ `config.action_mailer.default_url_options`, which is used for generating email
817
+ links.
606
818
 
607
- * `:account` Active Record model instance from which to set `account` and `session[:account_id]`
608
- * `:query` & `:form` – set specific query/form parameters
609
- * `:session` – set any session values
610
- * `:env` – set any additional Rack env values
819
+ For generating authentication URLs outside of a request use the
820
+ [path_class_methods] plugin:
611
821
 
612
822
  ```rb
613
- Rodauth::Rails.rodauth(account: Account.find(account_id))
614
- Rodauth::Rails.rodauth(query: { "param" => "value" })
615
- Rodauth::Rails.rodauth(form: { "param" => "value" })
616
- Rodauth::Rails.rodauth(session: { two_factor_auth_setup: true })
823
+ # app/lib/rodauth_app.rb
824
+ class RodauthApp < Rodauth::Rails::App
825
+ configure do
826
+ enable :path_class_methods
827
+ end
828
+ end
829
+ ```
830
+ ```rb
831
+ # main configuration
832
+ RodauthApp.rodauth.create_account_path
833
+ RodauthApp.rodauth.verify_account_url(key: "abc123")
834
+
835
+ # secondary configuration
836
+ RodauthApp.rodauth(:admin).close_account_path
837
+ ```
838
+
839
+ #### Calling instance methods
840
+
841
+ If you need to access Rodauth methods not exposed as internal requests, you can
842
+ use `Rodauth::Rails.rodauth` to retrieve the Rodauth instance used by the
843
+ internal_request feature:
844
+
845
+ ```rb
846
+ # app/lib/rodauth_app.rb
847
+ class RodauthApp < Rodauth::Rails::App
848
+ configure do
849
+ enable :internal_request # this is required
850
+ end
851
+ end
852
+ ```
853
+ ```rb
854
+ account = Account.find_by!(email: "user@example.com")
855
+ rodauth = Rodauth::Rails.rodauth(account: account)
856
+
857
+ rodauth.compute_hmac("token") #=> "TpEJTKfKwqYvIDKWsuZhkhKlhaBXtR1aodskBAflD8U"
858
+ rodauth.open_account? #=> true
859
+ rodauth.two_factor_authentication_setup? #=> true
860
+ rodauth.password_meets_requirements?("foo") #=> false
861
+ rodauth.locked_out? #=> false
862
+ ```
863
+
864
+ In addition to the `:account` option, the `Rodauth::Rails.rodauth`
865
+ method accepts any options supported by the internal_request feature.
866
+
867
+ ```rb
868
+ # main configuration
617
869
  Rodauth::Rails.rodauth(env: { "HTTP_USER_AGENT" => "programmatic" })
870
+ Rodauth::Rails.rodauth(session: { two_factor_auth_setup: true })
871
+
872
+ # secondary configuration
873
+ Rodauth::Rails.rodauth(:admin, params: { "param" => "value" })
618
874
  ```
619
875
 
620
876
  ## How it works
@@ -753,21 +1009,23 @@ class RodauthApp < Rodauth::Rails::App
753
1009
  end
754
1010
  ```
755
1011
 
756
- If you need Cross-Origin Resource Sharing and/or JWT refresh tokens, enable the
757
- corresponding Rodauth features and create the necessary tables:
1012
+ The JWT token will be returned after each request to Rodauth routes. To also
1013
+ return the JWT token on requests to your app's routes, you can add the
1014
+ following code to your base controller:
758
1015
 
759
- ```sh
760
- $ rails generate rodauth:migration jwt_refresh
761
- $ rails db:migrate
762
- ```
763
1016
  ```rb
764
- # app/lib/rodauth_app.rb
765
- class RodauthApp < Rodauth::Rails::App
766
- configure do
767
- # ...
768
- enable :jwt, :jwt_cors, :jwt_refresh
769
- # ...
1017
+ class ApplicationController < ActionController::Base
1018
+ # ...
1019
+ after_action :set_jwt_token
1020
+
1021
+ private
1022
+
1023
+ def set_jwt_token
1024
+ if rodauth.use_jwt? && rodauth.valid_jwt?
1025
+ response.headers["Authorization"] = rodauth.session_jwt
1026
+ end
770
1027
  end
1028
+ # ...
771
1029
  end
772
1030
  ```
773
1031
 
@@ -872,9 +1130,13 @@ class RodauthController < ApplicationController
872
1130
  account.identities.create!(provider: auth["provider"], uid: auth["uid"], info: auth["info"])
873
1131
  end
874
1132
 
875
- # login with Rodauth
1133
+ # load the account into the rodauth instance
876
1134
  rodauth.account_from_login(account.email)
877
- rodauth.login("omniauth")
1135
+
1136
+ rodauth_response do # ensures any `after_action` callbacks get called
1137
+ # sign in the loaded account
1138
+ rodauth.login("omniauth")
1139
+ end
878
1140
  end
879
1141
  end
880
1142
  ```
@@ -893,6 +1155,7 @@ methods:
893
1155
  | `rails_check_csrf!` | Verifies the authenticity token for the current request. |
894
1156
  | `rails_controller_instance` | Instance of the controller with the request env context. |
895
1157
  | `rails_controller` | Controller class to use for rendering and CSRF protection. |
1158
+ | `rails_account_model` | Model class connected with the accounts table. |
896
1159
 
897
1160
  The `Rodauth::Rails` module has a few config settings available as well:
898
1161
 
@@ -1075,29 +1338,6 @@ Rails.application.configure do |config|
1075
1338
  end
1076
1339
  ```
1077
1340
 
1078
- If you need to create an account record with a password directly, you can do it
1079
- as follows:
1080
-
1081
- ```rb
1082
- # app/models/account.rb
1083
- class Account < ApplicationRecord
1084
- has_one :password_hash, foreign_key: :id
1085
- end
1086
- ```
1087
- ```rb
1088
- # app/models/account/password_hash.rb
1089
- class Account::PasswordHash < ApplicationRecord
1090
- belongs_to :account, foreign_key: :id
1091
- end
1092
- ```
1093
- ```rb
1094
- require "bcrypt"
1095
-
1096
- account = Account.create!(email: "user@example.com", status: "verified")
1097
- password_hash = BCrypt::Password.create("secret", cost: BCrypt::Engine::MIN_COST)
1098
- account.create_password_hash!(id: account.id, password_hash: password_hash)
1099
- ```
1100
-
1101
1341
  ## Rodauth defaults
1102
1342
 
1103
1343
  rodauth-rails changes some of the default Rodauth settings for easier setup:
@@ -1231,3 +1471,6 @@ conduct](https://github.com/janko/rodauth-rails/blob/master/CODE_OF_CONDUCT.md).
1231
1471
  [single_session]: http://rodauth.jeremyevans.net/rdoc/files/doc/single_session_rdoc.html
1232
1472
  [account_expiration]: http://rodauth.jeremyevans.net/rdoc/files/doc/account_expiration_rdoc.html
1233
1473
  [simple_ldap_authenticator]: https://github.com/jeremyevans/simple_ldap_authenticator
1474
+ [internal_request]: http://rodauth.jeremyevans.net/rdoc/files/doc/internal_request_rdoc.html
1475
+ [composite_primary_keys]: https://github.com/composite-primary-keys/composite_primary_keys
1476
+ [path_class_methods]: https://rodauth.jeremyevans.net/rdoc/files/doc/path_class_methods_rdoc.html
@@ -52,7 +52,7 @@ class RodauthApp < Rodauth::Rails::App
52
52
  # reset_password_autologin? true
53
53
 
54
54
  # Delete the account record when the user has closed their account.
55
- # delete_account_on_close? true
55
+ delete_account_on_close? true
56
56
 
57
57
  # Redirect to the app from login and registration pages if already logged in.
58
58
  # already_logged_in { redirect login_redirect }
@@ -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 %>