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