rodauth-rails 0.12.0 → 0.16.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 +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>