rodauth-rails 0.18.1 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +34 -0
  3. data/README.md +219 -553
  4. data/lib/generators/rodauth/install_generator.rb +32 -35
  5. data/lib/generators/rodauth/migration_generator.rb +9 -2
  6. data/lib/generators/rodauth/templates/INSTRUCTIONS +40 -0
  7. data/lib/generators/rodauth/templates/app/mailers/rodauth_mailer.rb +36 -19
  8. data/lib/generators/rodauth/templates/app/misc/rodauth_app.rb +38 -0
  9. data/lib/generators/rodauth/templates/app/{lib/rodauth_app.rb → misc/rodauth_main.rb} +9 -52
  10. data/lib/generators/rodauth/templates/app/views/rodauth/_email_auth_request_form.html.erb +1 -1
  11. data/lib/generators/rodauth/templates/app/views/rodauth/change_login.html.erb +1 -1
  12. data/lib/generators/rodauth/templates/app/views/rodauth/change_password.html.erb +1 -1
  13. data/lib/generators/rodauth/templates/app/views/rodauth/close_account.html.erb +1 -1
  14. data/lib/generators/rodauth/templates/app/views/rodauth/confirm_password.html.erb +1 -1
  15. data/lib/generators/rodauth/templates/app/views/rodauth/create_account.html.erb +1 -1
  16. data/lib/generators/rodauth/templates/app/views/rodauth/email_auth.html.erb +1 -1
  17. data/lib/generators/rodauth/templates/app/views/rodauth/logout.html.erb +1 -1
  18. data/lib/generators/rodauth/templates/app/views/rodauth/otp_auth.html.erb +1 -1
  19. data/lib/generators/rodauth/templates/app/views/rodauth/otp_disable.html.erb +1 -1
  20. data/lib/generators/rodauth/templates/app/views/rodauth/otp_setup.html.erb +1 -1
  21. data/lib/generators/rodauth/templates/app/views/rodauth/recovery_auth.html.erb +1 -1
  22. data/lib/generators/rodauth/templates/app/views/rodauth/remember.html.erb +1 -1
  23. data/lib/generators/rodauth/templates/app/views/rodauth/reset_password.html.erb +1 -1
  24. data/lib/generators/rodauth/templates/app/views/rodauth/reset_password_request.html.erb +1 -1
  25. data/lib/generators/rodauth/templates/app/views/rodauth/sms_auth.html.erb +1 -1
  26. data/lib/generators/rodauth/templates/app/views/rodauth/sms_confirm.html.erb +1 -1
  27. data/lib/generators/rodauth/templates/app/views/rodauth/sms_disable.html.erb +1 -1
  28. data/lib/generators/rodauth/templates/app/views/rodauth/sms_request.html.erb +1 -1
  29. data/lib/generators/rodauth/templates/app/views/rodauth/sms_setup.html.erb +1 -1
  30. data/lib/generators/rodauth/templates/app/views/rodauth/two_factor_disable.html.erb +1 -1
  31. data/lib/generators/rodauth/templates/app/views/rodauth/unlock_account.html.erb +1 -1
  32. data/lib/generators/rodauth/templates/app/views/rodauth/unlock_account_request.html.erb +1 -1
  33. data/lib/generators/rodauth/templates/app/views/rodauth/verify_account.html.erb +1 -1
  34. data/lib/generators/rodauth/templates/app/views/rodauth/verify_account_resend.html.erb +1 -1
  35. data/lib/generators/rodauth/templates/app/views/rodauth/verify_login_change.html.erb +1 -1
  36. data/lib/generators/rodauth/templates/app/views/rodauth/webauthn_auth.html.erb +1 -1
  37. data/lib/generators/rodauth/templates/app/views/rodauth/webauthn_remove.html.erb +1 -1
  38. data/lib/generators/rodauth/templates/app/views/rodauth/webauthn_setup.html.erb +1 -1
  39. data/lib/rodauth/rails/app.rb +1 -4
  40. data/lib/rodauth/rails/auth.rb +1 -16
  41. data/lib/rodauth/rails/controller_methods.rb +1 -1
  42. data/lib/rodauth/rails/feature/internal_request.rb +10 -4
  43. data/lib/rodauth/rails/feature/render.rb +8 -0
  44. data/lib/rodauth/rails/tasks.rake +2 -2
  45. data/lib/rodauth/rails/version.rb +1 -1
  46. data/lib/rodauth/rails.rb +9 -20
  47. data/rodauth-rails.gemspec +1 -1
  48. metadata +7 -5
data/README.md CHANGED
@@ -8,6 +8,9 @@ Useful links:
8
8
 
9
9
  * [Rodauth documentation](http://rodauth.jeremyevans.net/documentation.html)
10
10
  * [Rails demo](https://github.com/janko/rodauth-demo-rails)
11
+ * [JSON API guide](https://github.com/janko/rodauth-rails/wiki/JSON-API)
12
+ * [OmniAuth guide](https://github.com/janko/rodauth-rails/wiki/OmniAuth)
13
+ * [Testing guide](https://github.com/janko/rodauth-rails/wiki/Testing)
11
14
 
12
15
  Articles:
13
16
 
@@ -39,17 +42,12 @@ Active Record. There are good reasons for this, and to make Rodauth work
39
42
  smoothly alongside Active Record, rodauth-rails configures Sequel to [reuse
40
43
  Active Record's database connection][sequel-activerecord_connection].
41
44
 
42
- ## Upgrading
43
-
44
- For instructions on upgrading from previous rodauth-rails versions, see
45
- [UPGRADING.md](/UPGRADING.md).
46
-
47
45
  ## Installation
48
46
 
49
47
  Add the gem to your Gemfile:
50
48
 
51
49
  ```rb
52
- gem "rodauth-rails", "~> 0.18"
50
+ gem "rodauth-rails", "~> 1.0"
53
51
 
54
52
  # gem "jwt", require: false # for JWT feature
55
53
  # gem "rotp", require: false # for OTP feature
@@ -74,9 +72,9 @@ $ rails generate rodauth:install --jwt # token authentication via the "Authoriza
74
72
  $ bundle add jwt
75
73
  ```
76
74
 
77
- This generator will create a Rodauth app with common authentication features
78
- enabled, a database migration with tables required by those features, a mailer
79
- with default templates, and a few other files.
75
+ This generator will create a Rodauth app and configuration with common
76
+ authentication features enabled, a database migration with tables required by
77
+ those features, a mailer with default templates, and a few other files.
80
78
 
81
79
  Feel free to remove any features you don't need, along with their corresponding
82
80
  tables. Afterwards, run the migration:
@@ -85,11 +83,23 @@ tables. Afterwards, run the migration:
85
83
  $ rails db:migrate
86
84
  ```
87
85
 
86
+ For your mailer to be able to generate email links, you'll need to set up
87
+ default URL options in each environment. Here is a possible configuration for
88
+ `config/environments/development.rb`:
89
+
90
+ ```rb
91
+ config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }
92
+ ```
93
+
88
94
  ## Usage
89
95
 
90
96
  ### Routes
91
97
 
92
- You can see the list of routes our Rodauth middleware handles:
98
+ Because requests to Rodauth endpoints are handled by the Rodauth middleware, and
99
+ not a Rails controller, Rodauth routes will not show in `rails routes`.
100
+
101
+ Use the `rodauth:routes` rake task to view the list of endpoints based on
102
+ currently loaded features:
93
103
 
94
104
  ```sh
95
105
  $ rails rodauth:routes
@@ -127,19 +137,6 @@ These routes are fully functional, feel free to visit them and interact with the
127
137
  pages. The templates that ship with Rodauth aim to provide a complete
128
138
  authentication experience, and the forms use [Bootstrap] markup.
129
139
 
130
- Inside Rodauth configuration and the `route` block you can access Rails route
131
- helpers through `#rails_routes`:
132
-
133
- ```rb
134
- class RodauthApp < Rodauth::Rails::App
135
- configure do
136
- # ...
137
- login_redirect { rails_routes.activity_path }
138
- # ...
139
- end
140
- end
141
- ```
142
-
143
140
  ### Current account
144
141
 
145
142
  The `#current_account` method is defined in controllers and views, which
@@ -160,13 +157,15 @@ configurations:
160
157
  current_account(:admin)
161
158
  ```
162
159
 
160
+ #### Custom account model
161
+
163
162
  The `#current_account` method will try to infer the account model class from
164
163
  the configured table name. If that fails, you can set the account model
165
164
  manually:
166
165
 
167
166
  ```rb
168
- # app/lib/rodauth_app.rb
169
- class RodauthApp < Rodauth::Rails::App
167
+ # app/misc/rodauth_main.rb
168
+ class RodauthMain < Rodauth::Rails::Auth
170
169
  configure do
171
170
  # ...
172
171
  rails_account_model Authentication::Account # custom model name
@@ -182,7 +181,7 @@ in your Rodauth app's routing block, which helps keep the authentication logic
182
181
  encapsulated:
183
182
 
184
183
  ```rb
185
- # app/lib/rodauth_app.rb
184
+ # app/misc/rodauth_app.rb
186
185
  class RodauthApp < Rodauth::Rails::App
187
186
  # ...
188
187
  route do |r|
@@ -305,12 +304,33 @@ Use `--name` to generate views for a different Rodauth configuration:
305
304
  $ rails generate rodauth:views webauthn --name admin
306
305
  ```
307
306
 
307
+ #### Page titles
308
+
309
+ The generated view templates use `content_for(:title)` to store Rodauth's page
310
+ titles, which you can then retrieve in your layout template to set the page
311
+ title:
312
+
313
+ ```erb
314
+ <!-- app/views/layouts/application.html.erb -->
315
+ <!DOCTYPE html>
316
+ <html>
317
+ <head>
318
+ <title><%= content_for(:title) %></title>
319
+ <!-- ... -->
320
+ </head>
321
+ <body>
322
+ <!-- ... -->
323
+ </body>
324
+ </html>
325
+ ```
326
+
308
327
  #### Layout
309
328
 
310
329
  To use different layouts for different Rodauth views, you can compare the
311
330
  request path in the layout method:
312
331
 
313
332
  ```rb
333
+ # app/controllers/rodauth_controller.rb
314
334
  class RodauthController < ApplicationController
315
335
  layout :rodauth_layout
316
336
 
@@ -331,6 +351,15 @@ class RodauthController < ApplicationController
331
351
  end
332
352
  ```
333
353
 
354
+ #### Turbo
355
+
356
+ [Turbo] has been disabled by default on all built-in and generated view
357
+ templates, because some Rodauth actions (multi-phase login, adding recovery
358
+ codes) aren't Turbo-compatible, as they return 200 responses on POST requests.
359
+
360
+ That being said, most of Rodauth *is* Turbo-compatible, so feel free to enable
361
+ Turbo for actions where you want to use it.
362
+
334
363
  ### Mailer
335
364
 
336
365
  The install generator will create `RodauthMailer` with default email templates,
@@ -340,48 +369,48 @@ flow to use it.
340
369
  ```rb
341
370
  # app/mailers/rodauth_mailer.rb
342
371
  class RodauthMailer < ApplicationMailer
343
- def verify_account(recipient, email_link)
372
+ def verify_account(account_id, key)
344
373
  # ...
345
374
  end
346
- def reset_password(recipient, email_link)
375
+ def reset_password(account_id, key)
347
376
  # ...
348
377
  end
349
- def verify_login_change(recipient, old_login, new_login, email_link)
378
+ def verify_login_change(account_id, old_login, new_login, key)
350
379
  # ...
351
380
  end
352
- def password_changed(recipient)
381
+ def password_changed(account_id)
353
382
  # ...
354
383
  end
355
- # def email_auth(recipient, email_link)
384
+ # def email_auth(account_id, key)
356
385
  # ...
357
386
  # end
358
- # def unlock_account(recipient, email_link)
387
+ # def unlock_account(account_id, key)
359
388
  # ...
360
389
  # end
361
390
  end
362
391
  ```
363
392
  ```rb
364
- # app/lib/rodauth_app.rb
365
- class RodauthApp < Rodauth::Rails::App
393
+ # app/misc/rodauth_main.rb
394
+ class RodauthMain < Rodauth::Rails::Auth
366
395
  configure do
367
396
  # ...
368
397
  create_reset_password_email do
369
- RodauthMailer.reset_password(email_to, reset_password_email_link)
398
+ RodauthMailer.reset_password(account_id, reset_password_key_value)
370
399
  end
371
400
  create_verify_account_email do
372
- RodauthMailer.verify_account(email_to, verify_account_email_link)
401
+ RodauthMailer.verify_account(account_id, verify_account_key_value)
373
402
  end
374
- create_verify_login_change_email do |login|
375
- RodauthMailer.verify_login_change(login, verify_login_change_old_login, verify_login_change_new_login, verify_login_change_email_link)
403
+ create_verify_login_change_email do |_login|
404
+ RodauthMailer.verify_login_change(account_id, verify_login_change_old_login, verify_login_change_new_login, verify_login_change_key_value)
376
405
  end
377
406
  create_password_changed_email do
378
- RodauthMailer.password_changed(email_to)
407
+ RodauthMailer.password_changed(account_id)
379
408
  end
380
409
  # create_email_auth_email do
381
- # RodauthMailer.email_auth(email_to, email_auth_email_link)
410
+ # RodauthMailer.email_auth(account_id, email_auth_key_value)
382
411
  # end
383
412
  # create_unlock_account_email do
384
- # RodauthMailer.unlock_account(email_to, unlock_account_email_link)
413
+ # RodauthMailer.unlock_account(account_id, unlock_account_key_value)
385
414
  # end
386
415
  send_email do |email|
387
416
  # queue email delivery on the mailer after the transaction commits
@@ -399,44 +428,8 @@ deliveries. However, if you want to send emails synchronously, you can modify
399
428
  the configuration to call `#deliver_now` instead.
400
429
 
401
430
  If you're using a background processing library without an Active Job adapter,
402
- or a 3rd-party service for sending transactional emails, this two-phase API
403
- might not be suitable. In this case, instead of overriding `#create_*_email`
404
- and `#send_email`, override the `#send_*_email` methods instead, which are
405
- required to send the email immediately. For example:
406
-
407
- ```rb
408
- # app/workers/rodauth_mailer_worker.rb
409
- class RodauthMailerWorker
410
- include Sidekiq::Worker
411
-
412
- def perform(name, *args)
413
- email = RodauthMailer.public_send(name, *args)
414
- email.deliver_now
415
- end
416
- end
417
- ```
418
- ```rb
419
- # app/lib/rodauth_app.rb
420
- class RodauthApp < Rodauth::Rails::App
421
- configure do
422
- # ...
423
- # use `#send_*_email` method to be able to immediately enqueue email delivery
424
- send_reset_password_email do
425
- enqueue_email(:reset_password, email_to, reset_password_email_link)
426
- end
427
- # ...
428
- auth_class_eval do
429
- # custom method for enqueuing email delivery using our worker
430
- def enqueue_email(name, *args)
431
- db.after_commit do
432
- RodauthMailerWorker.perform_async(name, *args)
433
- end
434
- end
435
- end
436
- # ...
437
- end
438
- end
439
- ```
431
+ or a 3rd-party service for sending transactional emails, see [this wiki
432
+ page][custom mailer worker] on how to set it up.
440
433
 
441
434
  ### Migrations
442
435
 
@@ -458,7 +451,23 @@ class CreateRodauthOtpSmsCodesRecoveryCodes < ActiveRecord::Migration
458
451
  end
459
452
  ```
460
453
 
461
- ### Model
454
+ #### Custom migration name
455
+
456
+ You can change the default migration name:
457
+
458
+ ```sh
459
+ $ rails generate rodauth:migration email_auth --name create_account_email_auth_keys
460
+ ```
461
+ ```rb
462
+ # db/migration/*_create_account_email_auth_keys
463
+ class CreateAccountEmailAuthKeys < ActiveRecord::Migration
464
+ def change
465
+ create_table :account_email_auth_keys do |t| ... end
466
+ end
467
+ end
468
+ ```
469
+
470
+ ## Model
462
471
 
463
472
  The `Rodauth::Rails::Model` mixin can be included into the account model, which
464
473
  defines a password attribute and associations for tables used by enabled
@@ -470,7 +479,7 @@ class Account < ApplicationRecord
470
479
  end
471
480
  ```
472
481
 
473
- #### Password attribute
482
+ ### Password attribute
474
483
 
475
484
  Regardless of whether you're storing the password hash in a column in the
476
485
  accounts table, or in a separate table, the `#password` attribute can be used
@@ -494,7 +503,7 @@ Note that the password attribute doesn't come with validations, making it
494
503
  unsuitable for forms. It was primarily intended to allow easily creating
495
504
  accounts in development console and in tests.
496
505
 
497
- #### Associations
506
+ ### Associations
498
507
 
499
508
  The `Rodauth::Rails::Model` mixin defines associations for Rodauth tables
500
509
  associated to the accounts table:
@@ -544,6 +553,8 @@ class Account < ApplicationRecord
544
553
  end
545
554
  ```
546
555
 
556
+ #### Association reference
557
+
547
558
  Below is a list of all associations defined depending on the features loaded:
548
559
 
549
560
  | Feature | Association | Type | Model | Table (default) |
@@ -568,6 +579,12 @@ Below is a list of all associations defined depending on the features loaded:
568
579
  | webauthn | `:webauthn_keys` | `has_many` | `WebauthnKey` | `account_webauthn_keys` |
569
580
  | webauthn | `:webauthn_user_id` | `has_one` | `WebauthnUserId` | `account_webauthn_user_ids` |
570
581
 
582
+ Note that some Rodauth tables use composite primary keys, which Active Record
583
+ doesn't support out of the box. For associations to work properly, you might
584
+ need to add the [composite_primary_keys] gem to your Gemfile.
585
+
586
+ #### Association options
587
+
571
588
  By default, all associations except for audit logs have `dependent: :destroy`
572
589
  set, to allow for easy deletion of account records in the console. You can use
573
590
  `:association_options` to modify global or per-association options:
@@ -582,31 +599,21 @@ Rodauth::Rails.model(association_options: -> (name) {
582
599
  })
583
600
  ```
584
601
 
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
-
589
- ### Multiple configurations
602
+ ## Multiple configurations
590
603
 
591
604
  If you need to handle multiple types of accounts that require different
592
- authentication logic, you can create additional configurations for them:
605
+ authentication logic, you can create new configurations for them. This
606
+ is done by creating new `Rodauth::Rails::Auth` subclasses, and registering
607
+ them under a name.
593
608
 
594
609
  ```rb
595
- # app/lib/rodauth_app.rb
610
+ # app/misc/rodauth_app.rb
596
611
  class RodauthApp < Rodauth::Rails::App
597
612
  # primary configuration
598
- configure do
599
- # ...
600
- end
613
+ configure RodauthMain
601
614
 
602
- # alternative configuration
603
- configure(:admin) do
604
- # ... enable features ...
605
- prefix "/admin"
606
- session_key_prefix "admin_"
607
- remember_cookie_key "_admin_remember" # if using remember feature
608
- # ...
609
- end
615
+ # secondary configuration
616
+ configure RodauthAdmin, :admin
610
617
 
611
618
  route do |r|
612
619
  r.rodauth
@@ -620,100 +627,46 @@ class RodauthApp < Rodauth::Rails::App
620
627
  end
621
628
  end
622
629
  ```
623
-
624
- Then in your application you can reference the secondary Rodauth instance:
625
-
626
630
  ```rb
627
- rodauth(:admin).login_path #=> "/admin/login"
628
- ```
629
-
630
- You'll likely want to save the information of which account belongs to which
631
- configuration to the database. One way would be to have a separate table that
632
- stores account types:
633
-
634
- ```sh
635
- $ rails generate migration create_account_types
636
- ```
637
- ```rb
638
- # db/migrate/*_create_account_types.rb
639
- class CreateAccountTypes < ActiveRecord::Migration
640
- def change
641
- create_table :account_types do |t|
642
- t.references :account, foreign_key: { on_delete: :cascade }, null: false
643
- t.string :type, null: false
644
- end
645
- end
646
- end
647
- ```
648
- ```sh
649
- $ rails db:migrate
650
- ```
651
-
652
- Then an entry would be inserted after account creation, and optionally whenever
653
- Rodauth retrieves accounts you could filter only those belonging to the current
654
- configuration:
655
-
656
- ```rb
657
- # app/lib/rodauth_app.rb
658
- class RodauthApp < Rodauth::Rails::App
659
- configure(:admin) do
660
- # ...
661
- after_create_account do
662
- db[:account_types].insert(account_id: account_id, type: "admin")
663
- end
664
- auth_class_eval do
665
- def account_ds(*)
666
- super.join(:account_types, account_id: :id).where(type: "admin")
667
- end
668
- end
669
- # ...
670
- end
671
- end
672
- ```
673
-
674
- #### Named auth classes
675
-
676
- A `configure` block inside `Rodauth::Rails::App` will internally create an
677
- anonymous `Rodauth::Auth` subclass, and register it under the given name.
678
- However, you can also define the auth classes explicitly, by creating
679
- subclasses of `Rodauth::Rails::Auth`:
680
-
681
- ```rb
682
- # app/lib/rodauth_main.rb
683
- class RodauthMain < Rodauth::Rails::Auth
684
- configure do
685
- # ... main configuration ...
686
- end
687
- end
688
- ```
689
- ```rb
690
- # app/lib/rodauth_admin.rb
631
+ # app/misc/rodauth_admin.rb
691
632
  class RodauthAdmin < Rodauth::Rails::Auth
692
633
  configure do
693
- # ...
634
+ # ... enable features ...
694
635
  prefix "/admin"
695
636
  session_key_prefix "admin_"
637
+ remember_cookie_key "_admin_remember" # if using remember feature
696
638
  # ...
639
+
640
+ # search views in `app/views/admin/rodauth` directory
641
+ rails_controller { Admin::RodauthController }
697
642
  end
698
643
  end
699
644
  ```
700
645
  ```rb
701
- # app/lib/rodauth_app.rb
702
- class RodauthApp < Rodauth::Rails::App
703
- configure RodauthMain
704
- configure RodauthAdmin, :admin
705
- # ...
646
+ # app/controllers/admin/rodauth_controller.rb
647
+ class Admin::RodauthController < ApplicationController
706
648
  end
707
649
  ```
708
650
 
709
- This allows having each configuration in a dedicated file, and named constants
710
- improve introspection and error messages. You can also use inheritance to share
711
- common settings:
651
+ Then in your application you can reference the secondary Rodauth instance:
652
+
653
+ ```rb
654
+ rodauth(:admin).login_path #=> "/admin/login"
655
+ ```
656
+
657
+ You'll likely want to save the information of which account belongs to which
658
+ configuration to the database. See [this guide][account types] on how you can do
659
+ that.
660
+
661
+ ### Sharing configuration
662
+
663
+ If there are common settings that you want to share between Rodauth
664
+ configurations, you can do so via inheritance:
712
665
 
713
666
  ```rb
714
- # app/lib/rodauth_base.rb
667
+ # app/misc/rodauth_base.rb
715
668
  class RodauthBase < Rodauth::Rails::Auth
716
- # common settings that can be shared between multiple configurations
669
+ # common settings that are shared between multiple configurations
717
670
  configure do
718
671
  enable :login, :logout
719
672
  login_return_to_requested_location? true
@@ -723,7 +676,7 @@ class RodauthBase < Rodauth::Rails::Auth
723
676
  end
724
677
  ```
725
678
  ```rb
726
- # app/lib/rodauth_main.rb
679
+ # app/misc/rodauth_main.rb
727
680
  class RodauthMain < RodauthBase # inherit common settings
728
681
  configure do
729
682
  # ... customize main ...
@@ -731,7 +684,7 @@ class RodauthMain < RodauthBase # inherit common settings
731
684
  end
732
685
  ```
733
686
  ```rb
734
- # app/lib/rodauth_admin.rb
687
+ # app/misc/rodauth_admin.rb
735
688
  class RodauthAdmin < RodauthBase # inherit common settings
736
689
  configure do
737
690
  # ... customize admin ...
@@ -739,112 +692,67 @@ class RodauthAdmin < RodauthBase # inherit common settings
739
692
  end
740
693
  ```
741
694
 
742
- Another benefit of explicit classes is that you can define custom methods
743
- directly at the class level instead of inside an `auth_class_eval`:
744
-
745
- ```rb
746
- # app/lib/rodauth_admin.rb
747
- class RodauthAdmin < Rodauth::Rails::Auth
748
- configure do
749
- # ...
750
- end
751
-
752
- def superadmin?
753
- Role.where(account_id: session_id, type: "superadmin").any?
754
- end
755
- end
756
- ```
757
- ```rb
758
- # config/routes.rb
759
- Rails.application.routes.draw do
760
- constraints Rodauth::Rails.authenticated(:admin) { |rodauth| rodauth.superadmin? } do
761
- mount Sidekiq::Web => "sidekiq"
762
- end
763
- end
764
- ```
765
-
766
- ### Calling controller methods
695
+ ## Outside of a request
767
696
 
768
- When using Rodauth before/after hooks or generally overriding your Rodauth
769
- configuration, in some cases you might want to call methods defined on your
770
- controllers. You can do so with `rails_controller_eval`, for example:
771
-
772
- ```rb
773
- # app/controllers/application_controller.rb
774
- class ApplicationController < ActionController::Base
775
- private
776
- def setup_tracking(account_id)
777
- # ... some implementation ...
778
- end
779
- end
780
- ```
781
- ```rb
782
- # app/lib/rodauth_app.rb
783
- class RodauthApp < Rodauth::Rails::App
784
- configure do
785
- after_create_account do
786
- rails_controller_eval { setup_tracking(account_id) }
787
- end
788
- end
789
- end
790
- ```
791
-
792
- ### Outside of a request
697
+ ### Calling actions
793
698
 
794
699
  In some cases you might need to use Rodauth more programmatically. If you want
795
700
  to perform authentication operations outside of request context, Rodauth ships
796
701
  with the [internal_request] feature just for that.
797
702
 
798
703
  ```rb
799
- # app/lib/rodauth_app.rb
800
- class RodauthApp < Rodauth::Rails::App
704
+ # app/misc/rodauth_main.rb
705
+ class RodauthMain < Rodauth::Rails::Auth
801
706
  configure do
802
707
  enable :internal_request
803
708
  end
804
709
  end
805
710
  ```
806
711
  ```rb
807
- # main configuration
712
+ # primary configuration
808
713
  RodauthApp.rodauth.create_account(login: "user@example.com", password: "secret")
809
714
  RodauthApp.rodauth.verify_account(account_login: "user@example.com")
810
715
 
811
716
  # secondary configuration
812
- RodauthApp.rodauth(:admin).close_account(account_login: "admin@example.com")
717
+ RodauthApp.rodauth(:admin).close_account(account_login: "user@example.com")
813
718
  ```
814
719
 
815
720
  The rodauth-rails gem additionally updates the internal rack env hash with your
816
721
  `config.action_mailer.default_url_options`, which is used for generating email
817
722
  links.
818
723
 
724
+ ### Generating URLs
725
+
819
726
  For generating authentication URLs outside of a request use the
820
727
  [path_class_methods] plugin:
821
728
 
822
729
  ```rb
823
- # app/lib/rodauth_app.rb
824
- class RodauthApp < Rodauth::Rails::App
730
+ # app/misc/rodauth_main.rb
731
+ class RodauthMain < Rodauth::Rails::Auth
825
732
  configure do
826
733
  enable :path_class_methods
734
+ create_account_route "register"
827
735
  end
828
736
  end
829
737
  ```
830
738
  ```rb
831
- # main configuration
832
- RodauthApp.rodauth.create_account_path
833
- RodauthApp.rodauth.verify_account_url(key: "abc123")
739
+ # primary configuration
740
+ RodauthApp.rodauth.create_account_path # => "/register"
741
+ RodauthApp.rodauth.verify_account_url(key: "abc123") #=> "https://example.com/verify-account?key=abc123"
834
742
 
835
743
  # secondary configuration
836
- RodauthApp.rodauth(:admin).close_account_path
744
+ RodauthApp.rodauth(:admin).close_account_path(foo: "bar") #=> "/admin/close-account?foo=bar"
837
745
  ```
838
746
 
839
- #### Calling instance methods
747
+ ### Calling instance methods
840
748
 
841
749
  If you need to access Rodauth methods not exposed as internal requests, you can
842
750
  use `Rodauth::Rails.rodauth` to retrieve the Rodauth instance used by the
843
751
  internal_request feature:
844
752
 
845
753
  ```rb
846
- # app/lib/rodauth_app.rb
847
- class RodauthApp < Rodauth::Rails::App
754
+ # app/misc/rodauth_main.rb
755
+ class RodauthMain < Rodauth::Rails::Auth
848
756
  configure do
849
757
  enable :internal_request # this is required
850
758
  end
@@ -852,7 +760,7 @@ end
852
760
  ```
853
761
  ```rb
854
762
  account = Account.find_by!(email: "user@example.com")
855
- rodauth = Rodauth::Rails.rodauth(account: account)
763
+ rodauth = Rodauth::Rails.rodauth(account: account) #=> #<RodauthMain::InternalRequest ...>
856
764
 
857
765
  rodauth.compute_hmac("token") #=> "TpEJTKfKwqYvIDKWsuZhkhKlhaBXtR1aodskBAflD8U"
858
766
  rodauth.open_account? #=> true
@@ -971,188 +879,10 @@ connection, using the [sequel-activerecord_connection] gem.
971
879
  This means that, from the usage perspective, Sequel can be considered just
972
880
  as an implementation detail of Rodauth.
973
881
 
974
- ## JSON API
975
-
976
- To make Rodauth endpoints accessible via JSON API, enable the [`json`][json]
977
- feature:
978
-
979
- ```rb
980
- # app/lib/rodauth_app.rb
981
- class RodauthApp < Rodauth::Rails::App
982
- configure do
983
- # ...
984
- enable :json
985
- only_json? true # accept only JSON requests (optional)
986
- # ...
987
- end
988
- end
989
- ```
990
-
991
- This will store account session data into the Rails session. If you rather want
992
- stateless token-based authentication via the `Authorization` header, enable the
993
- [`jwt`][jwt] feature (which builds on top of the `json` feature) and add the
994
- [JWT gem] to the Gemfile:
995
-
996
- ```sh
997
- $ bundle add jwt
998
- ```
999
- ```rb
1000
- # app/lib/rodauth_app.rb
1001
- class RodauthApp < Rodauth::Rails::App
1002
- configure do
1003
- # ...
1004
- enable :jwt
1005
- jwt_secret "<YOUR_SECRET_KEY>" # store the JWT secret in a safe place
1006
- only_json? true # accept only JSON requests (optional)
1007
- # ...
1008
- end
1009
- end
1010
- ```
1011
-
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:
1015
-
1016
- ```rb
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
1027
- end
1028
- # ...
1029
- end
1030
- ```
1031
-
1032
- ## OmniAuth
1033
-
1034
- While Rodauth doesn't yet come with [OmniAuth] integration, we can build one
1035
- ourselves using the existing Rodauth API.
1036
-
1037
- Let's assume we're building Facebook login. We'll start by installing the
1038
- necessary gems, and loading the Facebook OmniAuth strategy:
1039
-
1040
- ```rb
1041
- # Gemfile
1042
- gem "omniauth", "~> 2.0"
1043
- gem "omniauth-rails_csrf_protection" # https://github.com/omniauth/omniauth/wiki/Resolving-CVE-2015-9284
1044
- gem "omniauth-facebook"
1045
- ```
1046
- ```rb
1047
- # config/initializers/omniauth.rb
1048
- Rails.application.config.middleware.use OmniAuth::Builder do
1049
- provider :facebook, ENV["FACEBOOK_APP_ID"], ENV["FACEBOOK_APP_SECRET"],
1050
- scope: "email", callback_path: "/auth/facebook/callback"
1051
- end
1052
- ```
1053
-
1054
- Since users might potentially want to login with multiple external providers, let's
1055
- create an `account_identities` table that will have a many-to-one relationship
1056
- with the `accounts` table:
1057
-
1058
- ```sh
1059
- $ rails generate model AccountIdentity
1060
- ```
1061
- ```rb
1062
- # db/migrate/*_create_account_identities.rb
1063
- class CreateAccountIdentities < ActiveRecord::Migration
1064
- def change
1065
- create_table :account_identities do |t|
1066
- t.references :account, null: false, foreign_key: { on_delete: :cascade }
1067
- t.string :provider, null: false
1068
- t.string :uid, null: false
1069
- t.jsonb :info, null: false, default: {} # adjust JSON column type for your database
1070
-
1071
- t.timestamps
1072
-
1073
- t.index [:provider, :uid], unique: true
1074
- end
1075
- end
1076
- end
1077
- ```
1078
- ```rb
1079
- # app/models/account_identity.rb
1080
- class AcccountIdentity < ApplicationRecord
1081
- belongs_to :account
1082
- end
1083
- ```
1084
- ```rb
1085
- # app/models/account.rb
1086
- class Account < ApplicationRecord
1087
- has_many :identities, class_name: "AccountIdentity"
1088
- end
1089
- ```
1090
-
1091
- Next, let's add a POST button pointing to the request URL to our login form:
1092
-
1093
- ```erb
1094
- <%= button_to "Login via Facebook", "/auth/facebook",
1095
- method: :post, data: { turbo: false }, class: "btn btn-link p-0" %>
1096
- ```
1097
-
1098
- Finally, let's implement the OmniAuth callback endpoint on our Rodauth
1099
- controller:
1100
-
1101
- ```rb
1102
- # config/routes.rb
1103
- Rails.application.routes.draw do
1104
- # ...
1105
- get "/auth/:provider/callback", to: "rodauth#omniauth"
1106
- end
1107
- ```
1108
- ```rb
1109
- # app/controllres/rodauth_controller.rb
1110
- class RodauthController < ApplicationController
1111
- def omniauth
1112
- auth = request.env["omniauth.auth"]
1113
-
1114
- # attempt to find existing identity directly
1115
- identity = AccountIdentity.find_by(provider: auth["provider"], uid: auth["uid"])
1116
-
1117
- if identity
1118
- # update any external info changes
1119
- identity.update!(info: auth["info"])
1120
- # set account from identity
1121
- account = identity.account
1122
- end
1123
-
1124
- # attempt to find an existing account by email
1125
- account ||= Account.find_by(email: auth["info"]["email"])
1126
-
1127
- # disallow login if account is not verified
1128
- if account && account.status != rodauth.account_open_status_value
1129
- redirect_to rodauth.login_path, alert: rodauth.unverified_account_message
1130
- return
1131
- end
1132
-
1133
- # create new account if it doesn't exist
1134
- unless account
1135
- account = Account.create!(email: auth["info"]["email"], status: rodauth.account_open_status_value)
1136
- end
1137
-
1138
- # create new identity if it doesn't exist
1139
- unless identity
1140
- account.identities.create!(provider: auth["provider"], uid: auth["uid"], info: auth["info"])
1141
- end
1142
-
1143
- # load the account into the rodauth instance
1144
- rodauth.account_from_login(account.email)
1145
-
1146
- rodauth_response do # ensures any `after_action` callbacks get called
1147
- # sign in the loaded account
1148
- rodauth.login("omniauth")
1149
- end
1150
- end
1151
- end
1152
- ```
1153
-
1154
882
  ## Configuring
1155
883
 
884
+ ### Configuration methods
885
+
1156
886
  The `rails` feature rodauth-rails loads provides the following configuration
1157
887
  methods:
1158
888
 
@@ -1167,6 +897,8 @@ methods:
1167
897
  | `rails_controller` | Controller class to use for rendering and CSRF protection. |
1168
898
  | `rails_account_model` | Model class connected with the accounts table. |
1169
899
 
900
+ ### General configuration
901
+
1170
902
  The `Rodauth::Rails` module has a few config settings available as well:
1171
903
 
1172
904
  | Name | Description |
@@ -1185,169 +917,102 @@ end
1185
917
  For the list of configuration methods provided by Rodauth, see the [feature
1186
918
  documentation].
1187
919
 
1188
- ## Custom extensions
1189
-
1190
- When developing custom extensions for Rodauth inside your Rails project, it's
1191
- probably better to use plain modules, at least in the beginning, as Rodauth
1192
- feature design doesn't yet work well with Zeitwerk reloading.
920
+ ### Defining custom methods
1193
921
 
1194
- Here is an example of an LDAP authentication extension that uses the
1195
- [simple_ldap_authenticator] gem.
922
+ All Rodauth configuration methods are just syntax sugar for defining instance
923
+ methods on the auth class. You can also define your own custom methods on the
924
+ auth class:
1196
925
 
1197
926
  ```rb
1198
- # app/lib/rodauth_ldap.rb
1199
- module RodauthLdap
1200
- def require_bcrypt?
1201
- false
927
+ class RodauthMain < Rodauth::Rails::Auth
928
+ configure do
929
+ password_match? { |password| ldap_valid?(password) }
1202
930
  end
1203
931
 
1204
- def password_match?(password)
932
+ # Example external identities table
933
+ def identities
934
+ db[:account_identities].where(account_id: account_id).all
935
+ end
936
+
937
+ private
938
+
939
+ # Example LDAP authentication
940
+ def ldap_valid?(password)
1205
941
  SimpleLdapAuthenticator.valid?(account[:email], password)
1206
942
  end
1207
943
  end
1208
944
  ```
1209
945
  ```rb
1210
- # app/lib/rodauth_app.rb
1211
- class RodauthApp < Rodauth::Rails::App
1212
- configure do
1213
- # ...
1214
- auth_class_eval do
1215
- include RodauthLdap
1216
- end
1217
- # ...
1218
- end
1219
- end
946
+ rodauth.identities #=> [{ provider: "facebook", uid: "abc123", ... }, ...]
1220
947
  ```
1221
948
 
1222
- ## Testing
949
+ ### Rails URL helpers
1223
950
 
1224
- System (browser) tests for Rodauth actions could look something like this:
951
+ Inside Rodauth configuration and the `route` block you can access Rails route
952
+ helpers through `#rails_routes`:
1225
953
 
1226
954
  ```rb
1227
- # test/system/authentication_test.rb
1228
- require "test_helper"
1229
-
1230
- class AuthenticationTest < ActionDispatch::SystemTestCase
1231
- include ActiveJob::TestHelper
1232
- driven_by :rack_test
1233
-
1234
- test "creating and verifying an account" do
1235
- create_account
1236
- assert_match "An email has been sent to you with a link to verify your account", page.text
1237
-
1238
- verify_account
1239
- assert_match "Your account has been verified", page.text
955
+ # app/misc/rodauth_main.rb
956
+ class RodauthMain < Rodauth::Rails::Auth
957
+ configure do
958
+ login_redirect { rails_routes.activity_path }
1240
959
  end
960
+ end
961
+ ```
1241
962
 
1242
- test "logging in and logging out" do
1243
- create_account(verify: true)
1244
-
1245
- logout
1246
- assert_match "You have been logged out", page.text
963
+ ### Calling controller methods
1247
964
 
1248
- login
1249
- assert_match "You have been logged in", page.text
1250
- end
965
+ When using Rodauth before/after hooks or generally overriding your Rodauth
966
+ configuration, in some cases you might want to call methods defined on your
967
+ controllers. You can do so with `rails_controller_eval`, for example:
1251
968
 
969
+ ```rb
970
+ # app/controllers/application_controller.rb
971
+ class ApplicationController < ActionController::Base
1252
972
  private
1253
-
1254
- def create_account(email: "user@example.com", password: "secret", verify: false)
1255
- visit "/create-account"
1256
- fill_in "Login", with: email
1257
- fill_in "Password", with: password
1258
- fill_in "Confirm Password", with: password
1259
- click_on "Create Account"
1260
- verify_account if verify
1261
- end
1262
-
1263
- def verify_account
1264
- perform_enqueued_jobs # run enqueued email deliveries
1265
- email = ActionMailer::Base.deliveries.last
1266
- verify_account_link = email.body.to_s[/\S+verify-account\S+/]
1267
- visit verify_account_link
1268
- click_on "Verify Account"
1269
- end
1270
-
1271
- def login(email: "user@example.com", password: "secret")
1272
- visit "/login"
1273
- fill_in "Login", with: email
1274
- fill_in "Password", with: password
1275
- click_on "Login"
1276
- end
1277
-
1278
- def logout
1279
- visit "/logout"
1280
- click_on "Logout"
973
+ def setup_tracking(account_id)
974
+ # ... some implementation ...
1281
975
  end
1282
976
  end
1283
977
  ```
1284
-
1285
- While request tests in JSON API mode with JWT tokens could look something like
1286
- this:
1287
-
1288
978
  ```rb
1289
- # test/integration/authentication_test.rb
1290
- require "test_helper"
1291
-
1292
- class AuthenticationTest < ActionDispatch::IntegrationTest
1293
- test "creating and verifying an account" do
1294
- create_account
1295
- assert_response :success
1296
- assert_match "An email has been sent to you with a link to verify your account", JSON.parse(body)["success"]
1297
-
1298
- verify_account
1299
- assert_response :success
1300
- assert_match "Your account has been verified", JSON.parse(body)["success"]
1301
- end
1302
-
1303
- test "logging in and logging out" do
1304
- create_account(verify: true)
1305
-
1306
- logout
1307
- assert_response :success
1308
- assert_match "You have been logged out", JSON.parse(body)["success"]
1309
-
1310
- login
1311
- assert_response :success
1312
- assert_match "You have been logged in", JSON.parse(body)["success"]
979
+ # app/misc/rodauth_main.rb
980
+ class RodauthMain < Rodauth::Rails::Auth
981
+ configure do
982
+ after_create_account do
983
+ rails_controller_eval { setup_tracking(account_id) }
984
+ end
1313
985
  end
986
+ end
987
+ ```
1314
988
 
1315
- private
989
+ ### Single-file configuration
1316
990
 
1317
- def create_account(email: "user@example.com", password: "secret", verify: false)
1318
- post "/create-account", as: :json, params: { login: email, password: password, "password-confirm": password }
1319
- verify_account if verify
1320
- end
991
+ If you would prefer to have all Rodauth logic contained inside a single file,
992
+ you call `Rodauth::Rails::App.configure` with a block, which will create an
993
+ anonymous auth class.
1321
994
 
1322
- def verify_account
1323
- perform_enqueued_jobs # run enqueued email deliveries
1324
- email = ActionMailer::Base.deliveries.last
1325
- verify_account_key = email.body.to_s[/verify-account\?key=(\S+)/, 1]
1326
- post "/verify-account", as: :json, params: { key: verify_account_key }
995
+ ```rb
996
+ # app/misc/rodauth_app.rb
997
+ class RodauthApp < Rodauth::Rails::App
998
+ # primary configuration
999
+ configure do
1000
+ enable :login, :logout, :create_account, :verify_account
1001
+ # ...
1327
1002
  end
1328
1003
 
1329
- def login(email: "user@example.com", password: "secret")
1330
- post "/login", as: :json, params: { login: email, password: password }
1004
+ # secondary configuration
1005
+ configure(:admin) do
1006
+ enable :email_auth, :single_session
1007
+ # ...
1331
1008
  end
1332
1009
 
1333
- def logout
1334
- post "/logout", as: :json, headers: { "Authorization" => headers["Authorization"] }
1010
+ route do |r|
1011
+ # ...
1335
1012
  end
1336
1013
  end
1337
1014
  ```
1338
1015
 
1339
- If you're delivering emails in the background, make sure to set Active Job
1340
- queue adapter to `:test` or `:inline`:
1341
-
1342
- ```rb
1343
- # config/environments/test.rb
1344
- Rails.application.configure do |config|
1345
- # ...
1346
- config.active_job.queue_adapter = :test # or :inline
1347
- # ...
1348
- end
1349
- ```
1350
-
1351
1016
  ## Rodauth defaults
1352
1017
 
1353
1018
  rodauth-rails changes some of the default Rodauth settings for easier setup:
@@ -1454,7 +1119,6 @@ conduct](https://github.com/janko/rodauth-rails/blob/master/CODE_OF_CONDUCT.md).
1454
1119
  [Rodauth]: https://github.com/jeremyevans/rodauth
1455
1120
  [Sequel]: https://github.com/jeremyevans/sequel
1456
1121
  [feature documentation]: http://rodauth.jeremyevans.net/documentation.html
1457
- [JWT gem]: https://github.com/jwt/ruby-jwt
1458
1122
  [Bootstrap]: https://getbootstrap.com/
1459
1123
  [Roda]: http://roda.jeremyevans.net/
1460
1124
  [HMAC]: http://rodauth.jeremyevans.net/rdoc/files/README_rdoc.html#label-HMAC
@@ -1463,7 +1127,6 @@ conduct](https://github.com/janko/rodauth-rails/blob/master/CODE_OF_CONDUCT.md).
1463
1127
  [sequel-activerecord_connection]: https://github.com/janko/sequel-activerecord_connection
1464
1128
  [plugin options]: http://rodauth.jeremyevans.net/rdoc/files/README_rdoc.html#label-Plugin+Options
1465
1129
  [hmac]: http://rodauth.jeremyevans.net/rdoc/files/README_rdoc.html#label-HMAC
1466
- [OmniAuth]: https://github.com/omniauth/omniauth
1467
1130
  [otp]: http://rodauth.jeremyevans.net/rdoc/files/doc/otp_rdoc.html
1468
1131
  [sms_codes]: http://rodauth.jeremyevans.net/rdoc/files/doc/sms_codes_rdoc.html
1469
1132
  [recovery_codes]: http://rodauth.jeremyevans.net/rdoc/files/doc/recovery_codes_rdoc.html
@@ -1484,3 +1147,6 @@ conduct](https://github.com/janko/rodauth-rails/blob/master/CODE_OF_CONDUCT.md).
1484
1147
  [internal_request]: http://rodauth.jeremyevans.net/rdoc/files/doc/internal_request_rdoc.html
1485
1148
  [composite_primary_keys]: https://github.com/composite-primary-keys/composite_primary_keys
1486
1149
  [path_class_methods]: https://rodauth.jeremyevans.net/rdoc/files/doc/path_class_methods_rdoc.html
1150
+ [account types]: https://github.com/janko/rodauth-rails/wiki/Account-Types
1151
+ [custom mailer worker]: https://github.com/janko/rodauth-rails/wiki/Custom-Mailer-Worker
1152
+ [Turbo]: https://turbo.hotwired.dev/