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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +36 -0
- data/README.md +304 -98
- data/lib/generators/rodauth/templates/app/lib/rodauth_app.rb +8 -4
- data/lib/generators/rodauth/templates/app/models/account.rb +1 -0
- data/lib/generators/rodauth/templates/app/views/rodauth/_email_auth_request_form.html.erb +1 -1
- data/lib/generators/rodauth/templates/app/views/rodauth/_field.html.erb +2 -2
- data/lib/generators/rodauth/templates/app/views/rodauth/_field_error.html.erb +2 -2
- data/lib/generators/rodauth/templates/app/views/rodauth/_global_logout_field.html.erb +1 -1
- data/lib/generators/rodauth/templates/app/views/rodauth/_login_confirm_field.html.erb +1 -1
- data/lib/generators/rodauth/templates/app/views/rodauth/_login_display.html.erb +1 -1
- data/lib/generators/rodauth/templates/app/views/rodauth/_login_field.html.erb +1 -1
- data/lib/generators/rodauth/templates/app/views/rodauth/_login_form.html.erb +3 -3
- data/lib/generators/rodauth/templates/app/views/rodauth/_login_form_footer.html.erb +2 -2
- data/lib/generators/rodauth/templates/app/views/rodauth/_login_form_header.html.erb +2 -2
- data/lib/generators/rodauth/templates/app/views/rodauth/_login_hidden_field.html.erb +1 -1
- data/lib/generators/rodauth/templates/app/views/rodauth/_new_password_field.html.erb +1 -1
- data/lib/generators/rodauth/templates/app/views/rodauth/_otp_auth_code_field.html.erb +1 -1
- data/lib/generators/rodauth/templates/app/views/rodauth/_password_confirm_field.html.erb +1 -1
- data/lib/generators/rodauth/templates/app/views/rodauth/_password_field.html.erb +1 -1
- data/lib/generators/rodauth/templates/app/views/rodauth/_recovery_code_field.html.erb +1 -1
- data/lib/generators/rodauth/templates/app/views/rodauth/_recovery_codes_form.html.erb +4 -4
- data/lib/generators/rodauth/templates/app/views/rodauth/_sms_code_field.html.erb +1 -1
- data/lib/generators/rodauth/templates/app/views/rodauth/_sms_phone_field.html.erb +1 -1
- data/lib/generators/rodauth/templates/app/views/rodauth/add_recovery_codes.html.erb +2 -2
- data/lib/generators/rodauth/templates/app/views/rodauth/change_login.html.erb +3 -3
- data/lib/generators/rodauth/templates/app/views/rodauth/change_password.html.erb +3 -3
- data/lib/generators/rodauth/templates/app/views/rodauth/close_account.html.erb +2 -2
- data/lib/generators/rodauth/templates/app/views/rodauth/confirm_password.html.erb +1 -1
- data/lib/generators/rodauth/templates/app/views/rodauth/create_account.html.erb +4 -4
- data/lib/generators/rodauth/templates/app/views/rodauth/email_auth.html.erb +1 -1
- data/lib/generators/rodauth/templates/app/views/rodauth/logout.html.erb +2 -2
- data/lib/generators/rodauth/templates/app/views/rodauth/multi_phase_login.html.erb +1 -1
- data/lib/generators/rodauth/templates/app/views/rodauth/otp_auth.html.erb +1 -1
- data/lib/generators/rodauth/templates/app/views/rodauth/otp_disable.html.erb +2 -2
- data/lib/generators/rodauth/templates/app/views/rodauth/otp_setup.html.erb +7 -7
- data/lib/generators/rodauth/templates/app/views/rodauth/recovery_auth.html.erb +1 -1
- data/lib/generators/rodauth/templates/app/views/rodauth/remember.html.erb +4 -4
- data/lib/generators/rodauth/templates/app/views/rodauth/reset_password.html.erb +2 -2
- data/lib/generators/rodauth/templates/app/views/rodauth/reset_password_request.html.erb +2 -2
- data/lib/generators/rodauth/templates/app/views/rodauth/sms_auth.html.erb +1 -1
- data/lib/generators/rodauth/templates/app/views/rodauth/sms_confirm.html.erb +1 -1
- data/lib/generators/rodauth/templates/app/views/rodauth/sms_disable.html.erb +2 -2
- data/lib/generators/rodauth/templates/app/views/rodauth/sms_request.html.erb +1 -1
- data/lib/generators/rodauth/templates/app/views/rodauth/sms_setup.html.erb +2 -2
- data/lib/generators/rodauth/templates/app/views/rodauth/two_factor_auth.html.erb +1 -1
- data/lib/generators/rodauth/templates/app/views/rodauth/two_factor_disable.html.erb +2 -2
- data/lib/generators/rodauth/templates/app/views/rodauth/two_factor_manage.html.erb +6 -6
- data/lib/generators/rodauth/templates/app/views/rodauth/unlock_account.html.erb +2 -2
- data/lib/generators/rodauth/templates/app/views/rodauth/unlock_account_request.html.erb +1 -1
- data/lib/generators/rodauth/templates/app/views/rodauth/verify_account.html.erb +3 -3
- data/lib/generators/rodauth/templates/app/views/rodauth/verify_account_resend.html.erb +2 -2
- data/lib/generators/rodauth/templates/app/views/rodauth/verify_login_change.html.erb +1 -1
- data/lib/generators/rodauth/templates/app/views/rodauth/webauthn_auth.html.erb +7 -7
- data/lib/generators/rodauth/templates/app/views/rodauth/webauthn_remove.html.erb +5 -5
- data/lib/generators/rodauth/templates/app/views/rodauth/webauthn_setup.html.erb +7 -7
- data/lib/generators/rodauth/views_generator.rb +29 -4
- data/lib/rodauth/rails/auth.rb +10 -13
- data/lib/rodauth/rails/controller_methods.rb +43 -1
- data/lib/rodauth/rails/feature/base.rb +8 -8
- data/lib/rodauth/rails/feature/callbacks.rb +6 -2
- data/lib/rodauth/rails/feature/internal_request.rb +50 -0
- data/lib/rodauth/rails/feature/render.rb +7 -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 +30 -13
- data/rodauth-rails.gemspec +4 -1
- metadata +50 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e46466d584d7579c32e7d7e53335260dd137c04371f4b7c4680caa5c6a4e4147
|
4
|
+
data.tar.gz: c0be8bdc56f5214c885fc5ad990a0be511251cab6dbf9b0ec7aa3fbd8631d0c9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
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.
|
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
|
-
|
158
|
-
|
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
|
-
|
163
|
-
|
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
|
-
|
153
|
+
Pass the configuration name to retrieve accounts belonging to other Rodauth
|
154
|
+
configurations:
|
179
155
|
|
180
|
-
```
|
181
|
-
|
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,
|
294
|
-
|
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
|
-
|
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
|
-
|
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
|
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
|
-
|
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
|
-
###
|
778
|
+
### Outside of a request
|
600
779
|
|
601
|
-
In some cases you might need to use Rodauth more programmatically
|
602
|
-
Rodauth operations outside of
|
603
|
-
|
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
|
-
|
799
|
+
account = Account.find_by!(email: "user@example.com")
|
800
|
+
rodauth = Rodauth::Rails.rodauth(account: account)
|
607
801
|
|
608
|
-
rodauth.
|
609
|
-
rodauth.
|
610
|
-
rodauth.
|
611
|
-
rodauth.
|
612
|
-
rodauth.
|
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
|
-
|
616
|
-
|
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
|
-
|
755
|
-
|
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
|
-
|
763
|
-
|
764
|
-
|
765
|
-
|
766
|
-
|
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
|
-
#
|
1085
|
+
# load the account into the rodauth instance
|
874
1086
|
rodauth.account_from_login(account.email)
|
875
|
-
|
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
|
-
# ==>
|
157
|
+
# ==> Secondary configurations
|
158
158
|
# configure(:admin) do
|
159
|
-
#
|
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
|
-
# ==>
|
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
|
-
#
|
195
|
+
# break # allow the Rails app to handle other "/admin/*" requests
|
192
196
|
# end
|
193
197
|
end
|
194
198
|
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
|
8
|
-
aria: ({ invalid: "true", describedby: "#{name}_error_message" } if rodauth
|
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
|
2
|
-
<div class="invalid-feedback" id="<%%= name %>_error_message"><%%= rodauth
|
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
|
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
|
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
|
3
|
+
<%%= email_field_tag <%= rodauth %>.login_param, params[<%= rodauth %>.login_param], id: "login", readonly: true, class: "form-control-plaintext" %>
|
4
4
|
</div>
|