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 +4 -4
- data/CHANGELOG.md +42 -0
- data/README.md +248 -77
- data/lib/generators/rodauth/install_generator.rb +10 -2
- data/lib/generators/rodauth/migration/base.erb +8 -2
- data/lib/generators/rodauth/templates/app/lib/rodauth_app.rb +3 -8
- data/lib/generators/rodauth/templates/app/models/account.rb +1 -0
- data/lib/generators/rodauth/templates/app/views/rodauth/_recovery_codes_form.html.erb +1 -1
- data/lib/generators/rodauth/views_generator.rb +6 -2
- data/lib/rodauth/rails/auth.rb +2 -2
- data/lib/rodauth/rails/controller_methods.rb +42 -1
- data/lib/rodauth/rails/feature/base.rb +9 -0
- data/lib/rodauth/rails/feature/callbacks.rb +6 -2
- data/lib/rodauth/rails/feature/csrf.rb +15 -4
- data/lib/rodauth/rails/feature/internal_request.rb +46 -0
- data/lib/rodauth/rails/feature.rb +2 -0
- data/lib/rodauth/rails/model/associations.rb +195 -0
- data/lib/rodauth/rails/model.rb +101 -0
- data/lib/rodauth/rails/tasks.rake +5 -5
- data/lib/rodauth/rails/version.rb +1 -1
- data/lib/rodauth/rails.rb +32 -31
- data/rodauth-rails.gemspec +4 -1
- metadata +49 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 458a4a5552c124a1e7587837bbd57eed020857ae691bcc4b1f2e2639df774bb8
|
4
|
+
data.tar.gz: b19da17830585641950d026dff75ebd32ded19ad16dd5fb5c3b8a33c34b0a8f6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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.
|
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
|
-
|
146
|
-
|
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
|
-
|
151
|
-
|
152
|
-
|
149
|
+
current_account #=> #<Account id=123 email="user@example.com">
|
150
|
+
current_account.email #=> "user@example.com"
|
151
|
+
```
|
153
152
|
|
154
|
-
|
153
|
+
If the account doesn't exist in the database, the session will be cleared and
|
154
|
+
login required.
|
155
155
|
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
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
|
-
|
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
|
-
```
|
169
|
-
|
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
|
-
###
|
792
|
+
### Outside of a request
|
660
793
|
|
661
|
-
In some cases you might need to use Rodauth more programmatically
|
662
|
-
|
663
|
-
|
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
|
-
|
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
|
-
|
669
|
-
rodauth.
|
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
|
676
|
-
|
677
|
-
|
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
|
-
|
680
|
-
|
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
|
-
|
686
|
-
Rodauth::Rails
|
687
|
-
|
688
|
-
|
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
|
-
|
829
|
-
|
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
|
-
|
837
|
-
|
838
|
-
|
839
|
-
|
840
|
-
|
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
|
-
#
|
1133
|
+
# load the account into the rodauth instance
|
948
1134
|
rodauth.account_from_login(account.email)
|
949
|
-
|
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] ||
|
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
|
8
|
+
t.citext :email, null: false
|
9
9
|
<% else -%>
|
10
|
-
t.string :email, null: false
|
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
|
-
# ==>
|
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
|
-
# ==>
|
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
|
-
#
|
195
|
+
# break # allow the Rails app to handle other "/admin/*" requests
|
201
196
|
# end
|
202
197
|
end
|
203
198
|
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.
|
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
|
data/lib/rodauth/rails/auth.rb
CHANGED
@@ -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(
|
9
|
+
def self.inherited(subclass)
|
10
10
|
super
|
11
11
|
superclass = self
|
12
|
-
|
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, ¬_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
|