rodauth-rails 0.14.0 → 0.17.1

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d447d09fef8c29feb6240523286b8906049e85965f20a6410d1a475f913d9051
4
- data.tar.gz: bca9b6eadec6b32f2193291c6922467a554105d290ffd7b34bc2606d62121926
3
+ metadata.gz: 458a4a5552c124a1e7587837bbd57eed020857ae691bcc4b1f2e2639df774bb8
4
+ data.tar.gz: b19da17830585641950d026dff75ebd32ded19ad16dd5fb5c3b8a33c34b0a8f6
5
5
  SHA512:
6
- metadata.gz: 1f512f9fe9a3e22dcddf477d8906d1ea63a548241fd93b43bbcaf274ff39e0104e20f64c6a2836e5b243e812ffde654deae55a0beca69f4ba917cd5943da8a3c
7
- data.tar.gz: dbbd99959dfd42134cd3374f1f9767cf3e8d49327c195d4c35c4ecf281d0c3dad52db76b7e2fbf030c9e3ea2131bfcb1b6a120cc4a310983d8db564e63b97cda
6
+ metadata.gz: 01d5c774269d260e2805e0a2e4a509d9db81a3a966e94176cd6c5df6d808356feee48c5009b9692e0647034ae1c4d6ea3dc29627fa2000d05821a4402080601a
7
+ data.tar.gz: 1dde8d0ffb4605d3abf7893628a804eca7c0fbc94f2418176651dce81fac14eb4dfe1d165e315288a2a46373b818c795e15e9d58b5ad77f15b1fb5c3d9efc4be
data/CHANGELOG.md CHANGED
@@ -1,3 +1,45 @@
1
+ ## 0.17.1 (2021-10-20)
2
+
3
+ * Skip checking CSRF when request forgery protection wasn't loaded on the controller (@janko)
4
+
5
+ * Create partial unique index for `accounts.email` column when using `sqlite3` adapter (@janko)
6
+
7
+ * Revert setting `delete_account_on_close?` to `true` in generated `rodauth_app.rb` (@janko)
8
+
9
+ * Disable Turbo in `_recovery_codes_form.html.erb`, since viewing recovery codes isn't Turbo-compatible (@janko)
10
+
11
+ * Generate JSON configuration on `rodauth:install` for API-only with sessions enabled (@janko)
12
+
13
+ * Generate JWT configuration on `rodauth:install` only for API-only apps without sessions enabled (@janko)
14
+
15
+ * Don't generate JWT configuration when `rodauth:install --json` was run in API-only app (@janko)
16
+
17
+ * Use `config.action_mailer.default_url_options` in path_class_methods feature (@janko)
18
+
19
+ ## 0.17.0 (2021-10-05)
20
+
21
+ * Set `delete_account_on_close?` to `true` in generated `rodauth_app.rb` (@janko)
22
+
23
+ * Change default `:dependent` option for associations to `:delete`/`:delete_all` (@janko)
24
+
25
+ * Add `rails_account_model` configuration method for when the account model cannot be inferred (@janko)
26
+
27
+ ## 0.16.0 (2021-09-26)
28
+
29
+ * Add `#current_account` to methods defined on `ActionController::Base` (@janko)
30
+
31
+ * Add missing template for verify_login_change feature to `rodauth:views` generator (@janko)
32
+
33
+ * Add `#rodauth_response` controller method for converting rodauth responses into controller responses (@janko)
34
+
35
+ ## 0.15.0 (2021-07-29)
36
+
37
+ * Add `Rodauth::Rails::Model` mixin that defines password attribute and associations on the model (@janko)
38
+
39
+ * Add support for the new internal_request feature (@janko)
40
+
41
+ * Implement `Rodauth::Rails.rodauth` in terms of the internal_request feature (@janko)
42
+
1
43
  ## 0.14.0 (2021-07-10)
2
44
 
3
45
  * Speed up template rendering by only searching formats accepted by the request (@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.14"
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
@@ -453,6 +458,134 @@ class CreateRodauthOtpSmsCodesRecoveryCodes < ActiveRecord::Migration
453
458
  end
454
459
  ```
455
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
+
456
589
  ### Multiple configurations
457
590
 
458
591
  If you need to handle multiple types of accounts that require different
@@ -656,37 +789,88 @@ class RodauthApp < Rodauth::Rails::App
656
789
  end
657
790
  ```
658
791
 
659
- ### Rodauth instance
792
+ ### Outside of a request
660
793
 
661
- In some cases you might need to use Rodauth more programmatically, and perform
662
- Rodauth operations outside of the request context. rodauth-rails gives you a
663
- 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.
664
797
 
665
798
  ```rb
666
- 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")
667
810
 
668
- rodauth.login_url #=> "https://example.com/login"
669
- rodauth.account_from_login("user@example.com") # loads user by email
670
- rodauth.password_match?("secret") #=> true
671
- rodauth.setup_account_verification
672
- rodauth.close_account
811
+ # secondary configuration
812
+ RodauthApp.rodauth(:admin).close_account(account_login: "admin@example.com")
673
813
  ```
674
814
 
675
- The base URL is taken from Action Mailer's `default_url_options` setting if
676
- configured. The `Rodauth::Rails.rodauth` method accepts additional keyword
677
- 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.
678
818
 
679
- * `:account` Active Record model instance from which to set `account` and `session[:account_id]`
680
- * `:query` & `:form` – set specific query/form parameters
681
- * `:session` – set any session values
682
- * `:env` – set any additional Rack env values
819
+ For generating authentication URLs outside of a request use the
820
+ [path_class_methods] plugin:
683
821
 
684
822
  ```rb
685
- Rodauth::Rails.rodauth(account: Account.find(account_id))
686
- Rodauth::Rails.rodauth(query: { "param" => "value" })
687
- Rodauth::Rails.rodauth(form: { "param" => "value" })
688
- 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
689
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" })
690
874
  ```
691
875
 
692
876
  ## How it works
@@ -825,21 +1009,23 @@ class RodauthApp < Rodauth::Rails::App
825
1009
  end
826
1010
  ```
827
1011
 
828
- If you need Cross-Origin Resource Sharing and/or JWT refresh tokens, enable the
829
- 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:
830
1015
 
831
- ```sh
832
- $ rails generate rodauth:migration jwt_refresh
833
- $ rails db:migrate
834
- ```
835
1016
  ```rb
836
- # app/lib/rodauth_app.rb
837
- class RodauthApp < Rodauth::Rails::App
838
- configure do
839
- # ...
840
- enable :jwt, :jwt_cors, :jwt_refresh
841
- # ...
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
842
1027
  end
1028
+ # ...
843
1029
  end
844
1030
  ```
845
1031
 
@@ -944,9 +1130,13 @@ class RodauthController < ApplicationController
944
1130
  account.identities.create!(provider: auth["provider"], uid: auth["uid"], info: auth["info"])
945
1131
  end
946
1132
 
947
- # login with Rodauth
1133
+ # load the account into the rodauth instance
948
1134
  rodauth.account_from_login(account.email)
949
- 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
950
1140
  end
951
1141
  end
952
1142
  ```
@@ -965,6 +1155,7 @@ methods:
965
1155
  | `rails_check_csrf!` | Verifies the authenticity token for the current request. |
966
1156
  | `rails_controller_instance` | Instance of the controller with the request env context. |
967
1157
  | `rails_controller` | Controller class to use for rendering and CSRF protection. |
1158
+ | `rails_account_model` | Model class connected with the accounts table. |
968
1159
 
969
1160
  The `Rodauth::Rails` module has a few config settings available as well:
970
1161
 
@@ -1147,29 +1338,6 @@ Rails.application.configure do |config|
1147
1338
  end
1148
1339
  ```
1149
1340
 
1150
- If you need to create an account record with a password directly, you can do it
1151
- as follows:
1152
-
1153
- ```rb
1154
- # app/models/account.rb
1155
- class Account < ApplicationRecord
1156
- has_one :password_hash, foreign_key: :id
1157
- end
1158
- ```
1159
- ```rb
1160
- # app/models/account/password_hash.rb
1161
- class Account::PasswordHash < ApplicationRecord
1162
- belongs_to :account, foreign_key: :id
1163
- end
1164
- ```
1165
- ```rb
1166
- require "bcrypt"
1167
-
1168
- account = Account.create!(email: "user@example.com", status: "verified")
1169
- password_hash = BCrypt::Password.create("secret", cost: BCrypt::Engine::MIN_COST)
1170
- account.create_password_hash!(id: account.id, password_hash: password_hash)
1171
- ```
1172
-
1173
1341
  ## Rodauth defaults
1174
1342
 
1175
1343
  rodauth-rails changes some of the default Rodauth settings for easier setup:
@@ -1303,3 +1471,6 @@ conduct](https://github.com/janko/rodauth-rails/blob/master/CODE_OF_CONDUCT.md).
1303
1471
  [single_session]: http://rodauth.jeremyevans.net/rdoc/files/doc/single_session_rdoc.html
1304
1472
  [account_expiration]: http://rodauth.jeremyevans.net/rdoc/files/doc/account_expiration_rdoc.html
1305
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
@@ -95,11 +95,11 @@ module Rodauth
95
95
  end
96
96
 
97
97
  def json?
98
- options[:json]
98
+ options[:json] || api_only? && session_store? && !options[:jwt]
99
99
  end
100
100
 
101
101
  def jwt?
102
- options[:jwt] || Rodauth::Rails.api_only?
102
+ options[:jwt] || api_only? && !session_store? && !options[:json]
103
103
  end
104
104
 
105
105
  def migration_features
@@ -107,6 +107,14 @@ module Rodauth
107
107
  features << :remember unless jwt?
108
108
  features
109
109
  end
110
+
111
+ def session_store?
112
+ !!::Rails.application.config.session_store
113
+ end
114
+
115
+ def api_only?
116
+ Rodauth::Rails.api_only?
117
+ end
110
118
  end
111
119
  end
112
120
  end
@@ -5,11 +5,17 @@ enable_extension "citext"
5
5
  create_table :accounts<%= primary_key_type %> do |t|
6
6
  <% case activerecord_adapter -%>
7
7
  <% when "postgresql" -%>
8
- t.citext :email, null: false, index: { unique: true, where: "status IN ('unverified', 'verified')" }
8
+ t.citext :email, null: false
9
9
  <% else -%>
10
- t.string :email, null: false, index: { unique: true }
10
+ t.string :email, null: false
11
11
  <% end -%>
12
12
  t.string :status, null: false, default: "unverified"
13
+ <% case activerecord_adapter -%>
14
+ <% when "postgresql", "sqlite3" -%>
15
+ t.index :email, unique: true, where: "status IN ('unverified', 'verified')"
16
+ <% else -%>
17
+ t.index :email, unique: true
18
+ <% end -%>
13
19
  end
14
20
 
15
21
  # Used if storing password hashes in a separate table (default)
@@ -154,7 +154,7 @@ 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
159
  # # ... enable features ...
160
160
  # prefix "/admin"
@@ -163,11 +163,6 @@ class RodauthApp < Rodauth::Rails::App
163
163
  #
164
164
  # # search views in `app/views/admin/rodauth` directory
165
165
  # rails_controller { Admin::RodauthController }
166
- #
167
- # # use separate tables (requires creating the new tables)
168
- # methods.grep(/_table$/) do |table_method|
169
- # public_send(table_method) { :"admin_#{super()}" }
170
- # end
171
166
  # end
172
167
 
173
168
  route do |r|
@@ -189,7 +184,7 @@ class RodauthApp < Rodauth::Rails::App
189
184
  # rodauth.require_authentication
190
185
  # end
191
186
 
192
- # ==> Multiple configurations
187
+ # ==> Secondary configurations
193
188
  # r.on "admin" do
194
189
  # r.rodauth(:admin)
195
190
  #
@@ -197,7 +192,7 @@ class RodauthApp < Rodauth::Rails::App
197
192
  # rodauth(:admin).require_http_basic_auth
198
193
  # end
199
194
  #
200
- # r.pass # allow the Rails app to handle other "/admin/*" requests
195
+ # break # allow the Rails app to handle other "/admin/*" requests
201
196
  # end
202
197
  end
203
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 %>.recovery_codes_path, method: :post do %>
1
+ <%%= form_tag <%= rodauth %>.recovery_codes_path, method: :post, data: { turbo: false } do %>
2
2
  <%%= render "password_field" if <%= rodauth %>.two_factor_modifications_require_password? %>
3
3
  <%%= render "submit",
4
4
  value: <%= rodauth %>.recovery_codes_button || "View Authentication Recovery Codes",
@@ -61,6 +61,9 @@ module Rodauth
61
61
  _field _field_error _login_hidden_field _login_field _submit
62
62
  verify_account_resend verify_account
63
63
  ],
64
+ verify_login_change: %w[
65
+ _submit verify_login_change
66
+ ],
64
67
  lockout: %w[
65
68
  _login_hidden_field _submit unlock_account_request unlock_account
66
69
  ],
@@ -129,8 +132,9 @@ module Rodauth
129
132
  end
130
133
 
131
134
  def controller
132
- rodauth = Rodauth::Rails.rodauth(configuration_name)
133
- rodauth.rails_controller
135
+ rodauth = Rodauth::Rails.app.rodauth(configuration_name)
136
+ fail ArgumentError, "unknown rodauth configuration: #{configuration_name.inspect}" unless rodauth
137
+ rodauth.allocate.rails_controller
134
138
  end
135
139
 
136
140
  def configuration_name
@@ -6,10 +6,10 @@ module Rodauth
6
6
  # Base auth class that applies some default configuration and supports
7
7
  # multi-level inheritance.
8
8
  class Auth < Rodauth::Auth
9
- def self.inherited(auth_class)
9
+ def self.inherited(subclass)
10
10
  super
11
11
  superclass = self
12
- auth_class.class_eval do
12
+ subclass.class_eval do
13
13
  @roda_class = Rodauth::Rails.app
14
14
  @features = superclass.features.clone
15
15
  @routes = superclass.routes.clone
@@ -4,13 +4,54 @@ module Rodauth
4
4
  def self.included(controller)
5
5
  # ActionController::API doesn't have helper methods
6
6
  if controller.respond_to?(:helper_method)
7
- controller.helper_method :rodauth
7
+ controller.helper_method :rodauth, :current_account
8
8
  end
9
9
  end
10
10
 
11
11
  def rodauth(name = nil)
12
12
  request.env.fetch ["rodauth", *name].join(".")
13
13
  end
14
+
15
+ def current_account(name = nil)
16
+ model = rodauth(name).rails_account_model
17
+ id = rodauth(name).session_value
18
+
19
+ @current_account ||= {}
20
+ @current_account[name] ||= fetch_account(model, id) do
21
+ rodauth(name).clear_session
22
+ rodauth(name).login_required
23
+ end
24
+ end
25
+
26
+ private
27
+
28
+ def fetch_account(model, id, &not_found)
29
+ if defined?(ActiveRecord::Base) && model < ActiveRecord::Base
30
+ begin
31
+ model.find(id)
32
+ rescue ActiveRecord::RecordNotFound
33
+ not_found.call
34
+ end
35
+ elsif model < Sequel::Model
36
+ begin
37
+ model.with_pk!(id)
38
+ rescue Sequel::NoMatchingRow
39
+ not_found.call
40
+ end
41
+ else
42
+ fail Error, "unsupported model type: #{model}"
43
+ end
44
+ end
45
+
46
+ def rodauth_response
47
+ res = catch(:halt) { return yield }
48
+
49
+ self.status = res[0]
50
+ self.headers.merge! res[1]
51
+ self.response_body = res[2]
52
+
53
+ res
54
+ end
14
55
  end
15
56
  end
16
57
  end