rodauth-rails 0.14.0 → 0.17.1

Sign up to get free protection for your applications and to get access to all the features.
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