rodauth-rails 0.18.0 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (62) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +48 -0
  3. data/LICENSE.txt +1 -1
  4. data/README.md +366 -651
  5. data/lib/generators/rodauth/install_generator.rb +32 -35
  6. data/lib/generators/rodauth/migration/active_sessions.erb +2 -2
  7. data/lib/generators/rodauth/migration/audit_logging.erb +1 -1
  8. data/lib/generators/rodauth/migration/base.erb +2 -2
  9. data/lib/generators/rodauth/migration/email_auth.erb +1 -1
  10. data/lib/generators/rodauth/migration/otp.erb +1 -1
  11. data/lib/generators/rodauth/migration/password_expiration.erb +1 -1
  12. data/lib/generators/rodauth/migration/reset_password.erb +1 -1
  13. data/lib/generators/rodauth/migration/sms_codes.erb +1 -1
  14. data/lib/generators/rodauth/migration/verify_account.erb +2 -2
  15. data/lib/generators/rodauth/migration/webauthn.erb +1 -1
  16. data/lib/generators/rodauth/migration_generator.rb +9 -2
  17. data/lib/generators/rodauth/migration_helpers.rb +8 -0
  18. data/lib/generators/rodauth/templates/INSTRUCTIONS +40 -0
  19. data/lib/generators/rodauth/templates/app/mailers/rodauth_mailer.rb +36 -19
  20. data/lib/generators/rodauth/templates/app/misc/rodauth_app.rb +27 -0
  21. data/lib/generators/rodauth/templates/app/{lib/rodauth_app.rb → misc/rodauth_main.rb} +10 -56
  22. data/lib/generators/rodauth/templates/app/models/account.rb +1 -0
  23. data/lib/generators/rodauth/templates/app/views/rodauth/_email_auth_request_form.html.erb +1 -1
  24. data/lib/generators/rodauth/templates/app/views/rodauth/change_login.html.erb +1 -1
  25. data/lib/generators/rodauth/templates/app/views/rodauth/change_password.html.erb +1 -1
  26. data/lib/generators/rodauth/templates/app/views/rodauth/close_account.html.erb +1 -1
  27. data/lib/generators/rodauth/templates/app/views/rodauth/confirm_password.html.erb +1 -1
  28. data/lib/generators/rodauth/templates/app/views/rodauth/create_account.html.erb +1 -1
  29. data/lib/generators/rodauth/templates/app/views/rodauth/email_auth.html.erb +1 -1
  30. data/lib/generators/rodauth/templates/app/views/rodauth/logout.html.erb +1 -1
  31. data/lib/generators/rodauth/templates/app/views/rodauth/otp_auth.html.erb +1 -1
  32. data/lib/generators/rodauth/templates/app/views/rodauth/otp_disable.html.erb +1 -1
  33. data/lib/generators/rodauth/templates/app/views/rodauth/otp_setup.html.erb +1 -1
  34. data/lib/generators/rodauth/templates/app/views/rodauth/recovery_auth.html.erb +1 -1
  35. data/lib/generators/rodauth/templates/app/views/rodauth/remember.html.erb +1 -1
  36. data/lib/generators/rodauth/templates/app/views/rodauth/reset_password.html.erb +1 -1
  37. data/lib/generators/rodauth/templates/app/views/rodauth/reset_password_request.html.erb +1 -1
  38. data/lib/generators/rodauth/templates/app/views/rodauth/sms_auth.html.erb +1 -1
  39. data/lib/generators/rodauth/templates/app/views/rodauth/sms_confirm.html.erb +1 -1
  40. data/lib/generators/rodauth/templates/app/views/rodauth/sms_disable.html.erb +1 -1
  41. data/lib/generators/rodauth/templates/app/views/rodauth/sms_request.html.erb +1 -1
  42. data/lib/generators/rodauth/templates/app/views/rodauth/sms_setup.html.erb +1 -1
  43. data/lib/generators/rodauth/templates/app/views/rodauth/two_factor_disable.html.erb +1 -1
  44. data/lib/generators/rodauth/templates/app/views/rodauth/unlock_account.html.erb +1 -1
  45. data/lib/generators/rodauth/templates/app/views/rodauth/unlock_account_request.html.erb +1 -1
  46. data/lib/generators/rodauth/templates/app/views/rodauth/verify_account.html.erb +1 -1
  47. data/lib/generators/rodauth/templates/app/views/rodauth/verify_account_resend.html.erb +1 -1
  48. data/lib/generators/rodauth/templates/app/views/rodauth/verify_login_change.html.erb +1 -1
  49. data/lib/generators/rodauth/templates/app/views/rodauth/webauthn_auth.html.erb +1 -1
  50. data/lib/generators/rodauth/templates/app/views/rodauth/webauthn_remove.html.erb +1 -1
  51. data/lib/generators/rodauth/templates/app/views/rodauth/webauthn_setup.html.erb +1 -1
  52. data/lib/rodauth/rails/app.rb +18 -4
  53. data/lib/rodauth/rails/auth.rb +1 -16
  54. data/lib/rodauth/rails/controller_methods.rb +4 -29
  55. data/lib/rodauth/rails/feature/base.rb +21 -0
  56. data/lib/rodauth/rails/feature/internal_request.rb +10 -4
  57. data/lib/rodauth/rails/feature/render.rb +8 -0
  58. data/lib/rodauth/rails/tasks.rake +2 -2
  59. data/lib/rodauth/rails/version.rb +1 -1
  60. data/lib/rodauth/rails.rb +9 -20
  61. data/rodauth-rails.gemspec +2 -2
  62. metadata +10 -8
data/README.md CHANGED
@@ -8,12 +8,15 @@ 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
 
14
17
  * [Rodauth: A Refreshing Authentication Solution for Ruby](https://janko.io/rodauth-a-refreshing-authentication-solution-for-ruby/)
15
- * [Adding Authentication in Rails with Rodauth](https://janko.io/adding-authentication-in-rails-with-rodauth/)
16
- * [Adding Multifactor Authentication in Rails with Rodauth](https://janko.io/adding-multifactor-authentication-in-rails-with-rodauth/)
18
+ * [Rails Authentication with Rodauth](https://janko.io/adding-authentication-in-rails-with-rodauth/)
19
+ * [Multifactor Authentication in Rails with Rodauth](https://janko.io/adding-multifactor-authentication-in-rails-with-rodauth/)
17
20
  * [How to build an OIDC provider using rodauth-oauth on Rails](https://honeyryderchuck.gitlab.io/httpx/2021/03/15/oidc-provider-on-rails-using-rodauth-oauth.html)
18
21
 
19
22
  ## Why Rodauth?
@@ -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 a Rack middleware (and not
99
+ 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,32 +137,17 @@ 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
146
- returns the model instance of the currently logged in account.
143
+ returns the model instance of the currently logged in account. If the account
144
+ doesn't exist in the database, the session will be cleared.
147
145
 
148
146
  ```rb
149
147
  current_account #=> #<Account id=123 email="user@example.com">
150
148
  current_account.email #=> "user@example.com"
151
149
  ```
152
150
 
153
- If the account doesn't exist in the database, the session will be cleared and
154
- login required.
155
-
156
151
  Pass the configuration name to retrieve accounts belonging to other Rodauth
157
152
  configurations:
158
153
 
@@ -160,13 +155,17 @@ configurations:
160
155
  current_account(:admin)
161
156
  ```
162
157
 
158
+ This just delegates to the `#rails_account` method on the Rodauth object.
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|
@@ -249,6 +248,18 @@ Rails.application.routes.draw do
249
248
  end
250
249
  ```
251
250
 
251
+ The current account can be retrieved via the `#rails_account` method:
252
+
253
+ ```rb
254
+ # config/routes.rb
255
+ Rails.application.routes.draw do
256
+ # require user to be admin
257
+ constraints Rodauth::Rails.authenticated { |rodauth| rodauth.rails_account.admin? } do
258
+ # ...
259
+ end
260
+ end
261
+ ```
262
+
252
263
  You can specify the Rodauth configuration by passing the configuration name:
253
264
 
254
265
  ```rb
@@ -305,12 +316,33 @@ Use `--name` to generate views for a different Rodauth configuration:
305
316
  $ rails generate rodauth:views webauthn --name admin
306
317
  ```
307
318
 
319
+ #### Page titles
320
+
321
+ The generated view templates use `content_for(:title)` to store Rodauth's page
322
+ titles, which you can then retrieve in your layout template to set the page
323
+ title:
324
+
325
+ ```erb
326
+ <!-- app/views/layouts/application.html.erb -->
327
+ <!DOCTYPE html>
328
+ <html>
329
+ <head>
330
+ <title><%= content_for(:title) %></title>
331
+ <!-- ... -->
332
+ </head>
333
+ <body>
334
+ <!-- ... -->
335
+ </body>
336
+ </html>
337
+ ```
338
+
308
339
  #### Layout
309
340
 
310
341
  To use different layouts for different Rodauth views, you can compare the
311
342
  request path in the layout method:
312
343
 
313
344
  ```rb
345
+ # app/controllers/rodauth_controller.rb
314
346
  class RodauthController < ApplicationController
315
347
  layout :rodauth_layout
316
348
 
@@ -331,6 +363,15 @@ class RodauthController < ApplicationController
331
363
  end
332
364
  ```
333
365
 
366
+ #### Turbo
367
+
368
+ [Turbo] has been disabled by default on all built-in and generated view
369
+ templates, because some Rodauth actions (multi-phase login, adding recovery
370
+ codes) aren't Turbo-compatible, as they return 200 responses on POST requests.
371
+
372
+ That being said, most of Rodauth *is* Turbo-compatible, so feel free to enable
373
+ Turbo for actions where you want to use it.
374
+
334
375
  ### Mailer
335
376
 
336
377
  The install generator will create `RodauthMailer` with default email templates,
@@ -340,48 +381,48 @@ flow to use it.
340
381
  ```rb
341
382
  # app/mailers/rodauth_mailer.rb
342
383
  class RodauthMailer < ApplicationMailer
343
- def verify_account(recipient, email_link)
384
+ def verify_account(account_id, key)
344
385
  # ...
345
386
  end
346
- def reset_password(recipient, email_link)
387
+ def reset_password(account_id, key)
347
388
  # ...
348
389
  end
349
- def verify_login_change(recipient, old_login, new_login, email_link)
390
+ def verify_login_change(account_id, old_login, new_login, key)
350
391
  # ...
351
392
  end
352
- def password_changed(recipient)
393
+ def password_changed(account_id)
353
394
  # ...
354
395
  end
355
- # def email_auth(recipient, email_link)
396
+ # def email_auth(account_id, key)
356
397
  # ...
357
398
  # end
358
- # def unlock_account(recipient, email_link)
399
+ # def unlock_account(account_id, key)
359
400
  # ...
360
401
  # end
361
402
  end
362
403
  ```
363
404
  ```rb
364
- # app/lib/rodauth_app.rb
365
- class RodauthApp < Rodauth::Rails::App
405
+ # app/misc/rodauth_main.rb
406
+ class RodauthMain < Rodauth::Rails::Auth
366
407
  configure do
367
408
  # ...
368
409
  create_reset_password_email do
369
- RodauthMailer.reset_password(email_to, reset_password_email_link)
410
+ RodauthMailer.reset_password(account_id, reset_password_key_value)
370
411
  end
371
412
  create_verify_account_email do
372
- RodauthMailer.verify_account(email_to, verify_account_email_link)
413
+ RodauthMailer.verify_account(account_id, verify_account_key_value)
373
414
  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)
415
+ create_verify_login_change_email do |_login|
416
+ RodauthMailer.verify_login_change(account_id, verify_login_change_old_login, verify_login_change_new_login, verify_login_change_key_value)
376
417
  end
377
418
  create_password_changed_email do
378
- RodauthMailer.password_changed(email_to)
419
+ RodauthMailer.password_changed(account_id)
379
420
  end
380
421
  # create_email_auth_email do
381
- # RodauthMailer.email_auth(email_to, email_auth_email_link)
422
+ # RodauthMailer.email_auth(account_id, email_auth_key_value)
382
423
  # end
383
424
  # create_unlock_account_email do
384
- # RodauthMailer.unlock_account(email_to, unlock_account_email_link)
425
+ # RodauthMailer.unlock_account(account_id, unlock_account_key_value)
385
426
  # end
386
427
  send_email do |email|
387
428
  # queue email delivery on the mailer after the transaction commits
@@ -399,44 +440,8 @@ deliveries. However, if you want to send emails synchronously, you can modify
399
440
  the configuration to call `#deliver_now` instead.
400
441
 
401
442
  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
- ```
443
+ or a 3rd-party service for sending transactional emails, see [this wiki
444
+ page][custom mailer worker] on how to set it up.
440
445
 
441
446
  ### Migrations
442
447
 
@@ -458,7 +463,23 @@ class CreateRodauthOtpSmsCodesRecoveryCodes < ActiveRecord::Migration
458
463
  end
459
464
  ```
460
465
 
461
- ### Model
466
+ #### Custom migration name
467
+
468
+ You can change the default migration name:
469
+
470
+ ```sh
471
+ $ rails generate rodauth:migration email_auth --name create_account_email_auth_keys
472
+ ```
473
+ ```rb
474
+ # db/migration/*_create_account_email_auth_keys
475
+ class CreateAccountEmailAuthKeys < ActiveRecord::Migration
476
+ def change
477
+ create_table :account_email_auth_keys do |t| ... end
478
+ end
479
+ end
480
+ ```
481
+
482
+ ## Model
462
483
 
463
484
  The `Rodauth::Rails::Model` mixin can be included into the account model, which
464
485
  defines a password attribute and associations for tables used by enabled
@@ -470,7 +491,7 @@ class Account < ApplicationRecord
470
491
  end
471
492
  ```
472
493
 
473
- #### Password attribute
494
+ ### Password attribute
474
495
 
475
496
  Regardless of whether you're storing the password hash in a column in the
476
497
  accounts table, or in a separate table, the `#password` attribute can be used
@@ -494,7 +515,7 @@ Note that the password attribute doesn't come with validations, making it
494
515
  unsuitable for forms. It was primarily intended to allow easily creating
495
516
  accounts in development console and in tests.
496
517
 
497
- #### Associations
518
+ ### Associations
498
519
 
499
520
  The `Rodauth::Rails::Model` mixin defines associations for Rodauth tables
500
521
  associated to the accounts table:
@@ -544,6 +565,8 @@ class Account < ApplicationRecord
544
565
  end
545
566
  ```
546
567
 
568
+ #### Association reference
569
+
547
570
  Below is a list of all associations defined depending on the features loaded:
548
571
 
549
572
  | Feature | Association | Type | Model | Table (default) |
@@ -568,6 +591,12 @@ Below is a list of all associations defined depending on the features loaded:
568
591
  | webauthn | `:webauthn_keys` | `has_many` | `WebauthnKey` | `account_webauthn_keys` |
569
592
  | webauthn | `:webauthn_user_id` | `has_one` | `WebauthnUserId` | `account_webauthn_user_ids` |
570
593
 
594
+ Note that some Rodauth tables use composite primary keys, which Active Record
595
+ doesn't support out of the box. For associations to work properly, you might
596
+ need to add the [composite_primary_keys] gem to your Gemfile.
597
+
598
+ #### Association options
599
+
571
600
  By default, all associations except for audit logs have `dependent: :destroy`
572
601
  set, to allow for easy deletion of account records in the console. You can use
573
602
  `:association_options` to modify global or per-association options:
@@ -582,138 +611,64 @@ Rodauth::Rails.model(association_options: -> (name) {
582
611
  })
583
612
  ```
584
613
 
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
614
+ ## Multiple configurations
590
615
 
591
616
  If you need to handle multiple types of accounts that require different
592
- authentication logic, you can create additional configurations for them:
617
+ authentication logic, you can create new configurations for them. This
618
+ is done by creating new `Rodauth::Rails::Auth` subclasses, and registering
619
+ them under a name.
593
620
 
594
621
  ```rb
595
- # app/lib/rodauth_app.rb
622
+ # app/misc/rodauth_app.rb
596
623
  class RodauthApp < Rodauth::Rails::App
597
- # primary configuration
598
- configure do
599
- # ...
600
- end
624
+ configure RodauthMain # primary configuration
625
+ configure RodauthAdmin, :admin # secondary configuration
601
626
 
602
- # alternative configuration
603
- configure(:admin) do
627
+ route do |r|
628
+ r.rodauth # route primary rodauth requests
629
+ r.rodauth(:admin) # route secondary rodauth requests
630
+ end
631
+ end
632
+ ```
633
+ ```rb
634
+ # app/misc/rodauth_admin.rb
635
+ class RodauthAdmin < Rodauth::Rails::Auth
636
+ configure do
604
637
  # ... enable features ...
605
638
  prefix "/admin"
606
639
  session_key_prefix "admin_"
607
640
  remember_cookie_key "_admin_remember" # if using remember feature
608
- # ...
609
- end
610
641
 
611
- route do |r|
612
- r.rodauth
613
-
614
- r.on "admin" do
615
- r.rodauth(:admin)
616
- break # allow routing of other /admin/* requests to continue to Rails
617
- end
618
-
619
- # ...
642
+ # search views in `app/views/admin/rodauth` directory
643
+ rails_controller { Admin::RodauthController }
620
644
  end
621
645
  end
622
646
  ```
623
-
624
- Then in your application you can reference the secondary Rodauth instance:
625
-
626
647
  ```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
648
+ # app/controllers/admin/rodauth_controller.rb
649
+ class Admin::RodauthController < ApplicationController
646
650
  end
647
651
  ```
648
- ```sh
649
- $ rails db:migrate
650
- ```
651
652
 
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:
653
+ Then in your application you can reference the secondary Rodauth instance:
655
654
 
656
655
  ```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
656
+ rodauth(:admin).login_path #=> "/admin/login"
672
657
  ```
673
658
 
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`:
659
+ You'll likely want to save the information of which account belongs to which
660
+ configuration to the database. See [this guide][account types] on how you can do
661
+ that.
680
662
 
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
691
- class RodauthAdmin < Rodauth::Rails::Auth
692
- configure do
693
- # ...
694
- prefix "/admin"
695
- session_key_prefix "admin_"
696
- # ...
697
- end
698
- end
699
- ```
700
- ```rb
701
- # app/lib/rodauth_app.rb
702
- class RodauthApp < Rodauth::Rails::App
703
- configure RodauthMain
704
- configure RodauthAdmin, :admin
705
- # ...
706
- end
707
- ```
663
+ ### Sharing configuration
708
664
 
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:
665
+ If there are common settings that you want to share between Rodauth
666
+ configurations, you can do so via inheritance:
712
667
 
713
668
  ```rb
714
- # app/lib/rodauth_base.rb
669
+ # app/misc/rodauth_base.rb
715
670
  class RodauthBase < Rodauth::Rails::Auth
716
- # common settings that can be shared between multiple configurations
671
+ # common settings that are shared between multiple configurations
717
672
  configure do
718
673
  enable :login, :logout
719
674
  login_return_to_requested_location? true
@@ -723,7 +678,7 @@ class RodauthBase < Rodauth::Rails::Auth
723
678
  end
724
679
  ```
725
680
  ```rb
726
- # app/lib/rodauth_main.rb
681
+ # app/misc/rodauth_main.rb
727
682
  class RodauthMain < RodauthBase # inherit common settings
728
683
  configure do
729
684
  # ... customize main ...
@@ -731,7 +686,7 @@ class RodauthMain < RodauthBase # inherit common settings
731
686
  end
732
687
  ```
733
688
  ```rb
734
- # app/lib/rodauth_admin.rb
689
+ # app/misc/rodauth_admin.rb
735
690
  class RodauthAdmin < RodauthBase # inherit common settings
736
691
  configure do
737
692
  # ... customize admin ...
@@ -739,112 +694,63 @@ class RodauthAdmin < RodauthBase # inherit common settings
739
694
  end
740
695
  ```
741
696
 
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
767
-
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
- ```
697
+ ## Outside of a request
791
698
 
792
- ### Outside of a request
699
+ ### Calling actions
793
700
 
794
701
  In some cases you might need to use Rodauth more programmatically. If you want
795
702
  to perform authentication operations outside of request context, Rodauth ships
796
703
  with the [internal_request] feature just for that.
797
704
 
798
705
  ```rb
799
- # app/lib/rodauth_app.rb
800
- class RodauthApp < Rodauth::Rails::App
706
+ # app/misc/rodauth_main.rb
707
+ class RodauthMain < Rodauth::Rails::Auth
801
708
  configure do
802
709
  enable :internal_request
803
710
  end
804
711
  end
805
712
  ```
806
713
  ```rb
807
- # main configuration
714
+ # primary configuration
808
715
  RodauthApp.rodauth.create_account(login: "user@example.com", password: "secret")
809
716
  RodauthApp.rodauth.verify_account(account_login: "user@example.com")
810
717
 
811
718
  # secondary configuration
812
- RodauthApp.rodauth(:admin).close_account(account_login: "admin@example.com")
719
+ RodauthApp.rodauth(:admin).close_account(account_login: "user@example.com")
813
720
  ```
814
721
 
815
- The rodauth-rails gem additionally updates the internal rack env hash with your
816
- `config.action_mailer.default_url_options`, which is used for generating email
817
- links.
722
+ ### Generating URLs
818
723
 
819
724
  For generating authentication URLs outside of a request use the
820
725
  [path_class_methods] plugin:
821
726
 
822
727
  ```rb
823
- # app/lib/rodauth_app.rb
824
- class RodauthApp < Rodauth::Rails::App
728
+ # app/misc/rodauth_main.rb
729
+ class RodauthMain < Rodauth::Rails::Auth
825
730
  configure do
826
731
  enable :path_class_methods
732
+ create_account_route "register"
827
733
  end
828
734
  end
829
735
  ```
830
736
  ```rb
831
- # main configuration
832
- RodauthApp.rodauth.create_account_path
833
- RodauthApp.rodauth.verify_account_url(key: "abc123")
737
+ # primary configuration
738
+ RodauthApp.rodauth.create_account_path # => "/register"
739
+ RodauthApp.rodauth.verify_account_url(key: "abc123") #=> "https://example.com/verify-account?key=abc123"
834
740
 
835
741
  # secondary configuration
836
- RodauthApp.rodauth(:admin).close_account_path
742
+ RodauthApp.rodauth(:admin).close_account_path(foo: "bar") #=> "/admin/close-account?foo=bar"
837
743
  ```
838
744
 
839
- #### Calling instance methods
745
+ ### Calling instance methods
840
746
 
841
747
  If you need to access Rodauth methods not exposed as internal requests, you can
842
748
  use `Rodauth::Rails.rodauth` to retrieve the Rodauth instance used by the
843
749
  internal_request feature:
844
750
 
845
751
  ```rb
846
- # app/lib/rodauth_app.rb
847
- class RodauthApp < Rodauth::Rails::App
752
+ # app/misc/rodauth_main.rb
753
+ class RodauthMain < Rodauth::Rails::Auth
848
754
  configure do
849
755
  enable :internal_request # this is required
850
756
  end
@@ -852,7 +758,7 @@ end
852
758
  ```
853
759
  ```rb
854
760
  account = Account.find_by!(email: "user@example.com")
855
- rodauth = Rodauth::Rails.rodauth(account: account)
761
+ rodauth = Rodauth::Rails.rodauth(account: account) #=> #<RodauthMain::InternalRequest ...>
856
762
 
857
763
  rodauth.compute_hmac("token") #=> "TpEJTKfKwqYvIDKWsuZhkhKlhaBXtR1aodskBAflD8U"
858
764
  rodauth.open_account? #=> true
@@ -873,480 +779,290 @@ Rodauth::Rails.rodauth(session: { two_factor_auth_setup: true })
873
779
  Rodauth::Rails.rodauth(:admin, params: { "param" => "value" })
874
780
  ```
875
781
 
876
- ## How it works
782
+ ## Configuring
877
783
 
878
- ### Middleware
784
+ ### Configuration methods
879
785
 
880
- rodauth-rails inserts a `Rodauth::Rails::Middleware` into your middleware
881
- stack, which calls your Rodauth app for each request, before the request
882
- reaches the Rails router.
786
+ The `rails` feature rodauth-rails loads provides the following configuration
787
+ methods:
883
788
 
884
- ```sh
885
- $ rails middleware
886
- ...
887
- use Rodauth::Rails::Middleware
888
- run MyApp::Application.routes
889
- ```
789
+ | Name | Description |
790
+ | :---- | :---------- |
791
+ | `rails_render(**options)` | Renders the template with given render options. |
792
+ | `rails_csrf_tag` | Hidden field added to Rodauth templates containing the CSRF token. |
793
+ | `rails_csrf_param` | Value of the `name` attribute for the CSRF tag. |
794
+ | `rails_csrf_token` | Value of the `value` attribute for the CSRF tag. |
795
+ | `rails_check_csrf!` | Verifies the authenticity token for the current request. |
796
+ | `rails_controller_instance` | Instance of the controller with the request env context. |
797
+ | `rails_controller` | Controller class to use for rendering and CSRF protection. |
798
+ | `rails_account_model` | Model class connected with the accounts table. |
890
799
 
891
- The Rodauth app stores the `Rodauth::Auth` instance in the Rack env hash, which
892
- is then available in your Rails app:
800
+ For the list of configuration methods provided by Rodauth, see the [feature
801
+ documentation].
893
802
 
894
- ```rb
895
- request.env["rodauth"] #=> #<Rodauth::Auth>
896
- request.env["rodauth.admin"] #=> #<Rodauth::Auth> (if using multiple configurations)
897
- ```
803
+ ### Defining custom methods
898
804
 
899
- For convenience, this object can be accessed via the `#rodauth` method in views
900
- and controllers:
805
+ All Rodauth configuration methods are just syntax sugar for defining instance
806
+ methods on the auth class. You can also define your own custom methods on the
807
+ auth class:
901
808
 
902
809
  ```rb
903
- class MyController < ApplicationController
904
- def my_action
905
- rodauth #=> #<Rodauth::Auth>
906
- rodauth(:admin) #=> #<Rodauth::Auth> (if using multiple configurations)
810
+ class RodauthMain < Rodauth::Rails::Auth
811
+ configure do
812
+ # ...
813
+ password_match? { |password| ldap_valid?(password) }
814
+ # ...
907
815
  end
908
- end
909
- ```
910
- ```erb
911
- <% rodauth #=> #<Rodauth::Auth> %>
912
- <% rodauth(:admin) #=> #<Rodauth::Auth> (if using multiple configurations) %>
913
- ```
914
-
915
- ### App
916
-
917
- The `Rodauth::Rails::App` class is a [Roda] subclass that provides Rails
918
- integration for Rodauth:
919
816
 
920
- * uses Action Dispatch flash instead of Roda's
921
- * uses Action Dispatch CSRF protection instead of Roda's
922
- * sets [HMAC] secret to Rails' secret key base
923
- * uses Action Controller for rendering templates
924
- * runs Action Controller callbacks & rescue handlers around Rodauth actions
925
- * uses Action Mailer for sending emails
817
+ # Example external identities table
818
+ def identities
819
+ db[:account_identities].where(account_id: account_id).all
820
+ end
926
821
 
927
- The `configure` method wraps configuring the Rodauth plugin, forwarding
928
- any additional [plugin options].
822
+ private
929
823
 
930
- ```rb
931
- class RodauthApp < Rodauth::Rails::App
932
- configure { ... } # defining default Rodauth configuration
933
- configure(json: true) { ... } # passing options to the Rodauth plugin
934
- configure(:admin) { ... } # defining multiple Rodauth configurations
824
+ # Example LDAP authentication
825
+ def ldap_valid?(password)
826
+ SimpleLdapAuthenticator.valid?(account[:email], password)
827
+ end
935
828
  end
936
829
  ```
830
+ ```rb
831
+ rodauth.identities #=> [{ provider: "facebook", uid: "abc123", ... }, ...]
832
+ ```
937
833
 
938
- The `route` block is provided by Roda, and it's called on each request before
939
- it reaches the Rails router.
834
+ ### Rails URL helpers
835
+
836
+ Inside Rodauth configuration and the `route` block you can access Rails route
837
+ helpers through `#rails_routes`:
940
838
 
941
839
  ```rb
942
- class RodauthApp < Rodauth::Rails::App
943
- route do |r|
944
- # ... called before each request ...
840
+ # app/misc/rodauth_main.rb
841
+ class RodauthMain < Rodauth::Rails::Auth
842
+ configure do
843
+ login_redirect { rails_routes.activity_path }
945
844
  end
946
845
  end
947
846
  ```
948
847
 
949
- Since `Rodauth::Rails::App` is just a Roda subclass, you can do anything you
950
- would with a Roda app, such as loading additional Roda plugins:
848
+ ### Calling controller methods
849
+
850
+ When using Rodauth before/after hooks or generally overriding your Rodauth
851
+ configuration, in some cases you might want to call methods defined on your
852
+ controllers. You can do so with `rails_controller_eval`, for example:
951
853
 
952
854
  ```rb
953
- class RodauthApp < Rodauth::Rails::App
954
- plugin :request_headers # easier access to request headers
955
- plugin :typecast_params # methods for conversion of request params
956
- plugin :default_headers, { "Foo" => "Bar" }
957
- # ...
855
+ # app/controllers/application_controller.rb
856
+ class ApplicationController < ActionController::Base
857
+ private
858
+ def setup_tracking(account_id)
859
+ # ... some implementation ...
860
+ end
958
861
  end
959
862
  ```
960
-
961
- ### Sequel
962
-
963
- Rodauth uses the [Sequel] library for database queries, due to more advanced
964
- database usage (SQL expressions, database-agnostic date arithmetic, SQL
965
- function calls).
966
-
967
- If ActiveRecord is used in the application, the `rodauth:install` generator
968
- will have automatically configured Sequel to reuse ActiveRecord's database
969
- connection, using the [sequel-activerecord_connection] gem.
970
-
971
- This means that, from the usage perspective, Sequel can be considered just
972
- as an implementation detail of Rodauth.
973
-
974
- ## JSON API
975
-
976
- To make Rodauth endpoints accessible via JSON API, enable the [`json`][json]
977
- feature:
978
-
979
863
  ```rb
980
- # app/lib/rodauth_app.rb
981
- class RodauthApp < Rodauth::Rails::App
864
+ # app/misc/rodauth_main.rb
865
+ class RodauthMain < Rodauth::Rails::Auth
982
866
  configure do
983
- # ...
984
- enable :json
985
- only_json? true # accept only JSON requests (optional)
986
- # ...
867
+ after_create_account do
868
+ rails_controller_eval { setup_tracking(account_id) }
869
+ end
987
870
  end
988
871
  end
989
872
  ```
990
873
 
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:
874
+ ### Single-file configuration
875
+
876
+ If you would prefer to have all Rodauth logic contained inside a single file,
877
+ you call `Rodauth::Rails::App.configure` with a block, which will create an
878
+ anonymous auth class.
995
879
 
996
- ```sh
997
- $ bundle add jwt
998
- ```
999
880
  ```rb
1000
- # app/lib/rodauth_app.rb
881
+ # app/misc/rodauth_app.rb
1001
882
  class RodauthApp < Rodauth::Rails::App
883
+ # primary configuration
1002
884
  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)
885
+ enable :login, :logout, :create_account, :verify_account
1007
886
  # ...
1008
887
  end
1009
- end
1010
- ```
1011
888
 
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
889
+ # secondary configuration
890
+ configure(:admin) do
891
+ enable :email_auth, :single_session
892
+ # ...
893
+ end
1022
894
 
1023
- def set_jwt_token
1024
- if rodauth.use_jwt? && rodauth.valid_jwt?
1025
- response.headers["Authorization"] = rodauth.session_jwt
1026
- end
895
+ route do |r|
896
+ # ...
1027
897
  end
1028
- # ...
1029
898
  end
1030
899
  ```
1031
900
 
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:
901
+ ## How it works
1039
902
 
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
- ```
903
+ ### Rack middleware
1053
904
 
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:
905
+ The railtie inserts [`Rodauth::Rails::Middleware`](/lib/rodauth/rails/middleware.rb)
906
+ at the end of the middleware stack, which calls your Rodauth app around each request.
1057
907
 
1058
908
  ```sh
1059
- $ rails generate model AccountIdentity
909
+ $ rails middleware
910
+ # ...
911
+ # use Rodauth::Rails::Middleware
912
+ # run MyApp::Application.routes
1060
913
  ```
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
914
 
1071
- t.timestamps
915
+ It can be inserted at any point in the middleware stack:
1072
916
 
1073
- t.index [:provider, :uid], unique: true
1074
- end
1075
- end
1076
- end
1077
- ```
1078
917
  ```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"
918
+ Rodauth::Rails.configure do |config|
919
+ config.middleware = false # disable auto-insertion
1088
920
  end
1089
- ```
1090
-
1091
- Next, let's add a POST button pointing to the request URL to our login form:
1092
921
 
1093
- ```erb
1094
- <%= button_to "Login via Facebook", "/auth/facebook",
1095
- method: :post, data: { turbo: false }, class: "btn btn-link p-0" %>
922
+ Rails.application.config.middleware.insert_before AnotherMiddleware, Rodauth::Rails::Middleware
1096
923
  ```
1097
924
 
1098
- Finally, let's implement the OmniAuth callback endpoint on our Rodauth
1099
- controller:
925
+ The middleware retrieves the Rodauth app via `Rodauth::Rails.app`, which is
926
+ specified as a string to keep the class autoloadable and reloadable in
927
+ development.
1100
928
 
1101
929
  ```rb
1102
- # config/routes.rb
1103
- Rails.application.routes.draw do
1104
- # ...
1105
- get "/auth/:provider/callback", to: "rodauth#omniauth"
930
+ Rodauth::Rails.configure do |config|
931
+ config.app = "RodauthApp"
1106
932
  end
1107
933
  ```
1108
- ```rb
1109
- # app/controllres/rodauth_controller.rb
1110
- class RodauthController < ApplicationController
1111
- def omniauth
1112
- auth = request.env["omniauth.auth"]
1113
934
 
1114
- # attempt to find existing identity directly
1115
- identity = AccountIdentity.find_by(provider: auth["provider"], uid: auth["uid"])
935
+ In addition to Zeitwerk compatibility, this extra layer catches Rodauth redirects
936
+ that happen on the controller level (e.g. when calling
937
+ `rodauth.require_authentication` in a `before_action` filter).
1116
938
 
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
939
+ ### Roda app
1123
940
 
1124
- # attempt to find an existing account by email
1125
- account ||= Account.find_by(email: auth["info"]["email"])
941
+ The [`Rodauth::Rails::App`](/lib/rodauth/rails/app.rb) class is a [Roda]
942
+ subclass that provides a convenience layer for Rodauth:
1126
943
 
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
944
+ * uses Action Dispatch flash messages
945
+ * provides syntax sugar for loading the rodauth plugin
946
+ * saves Rodauth object(s) to Rack env hash
947
+ * propagates edited headers to Rails responses
1132
948
 
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
949
+ #### Configure block
1137
950
 
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
- ## Configuring
1155
-
1156
- The `rails` feature rodauth-rails loads provides the following configuration
1157
- methods:
1158
-
1159
- | Name | Description |
1160
- | :---- | :---------- |
1161
- | `rails_render(**options)` | Renders the template with given render options. |
1162
- | `rails_csrf_tag` | Hidden field added to Rodauth templates containing the CSRF token. |
1163
- | `rails_csrf_param` | Value of the `name` attribute for the CSRF tag. |
1164
- | `rails_csrf_token` | Value of the `value` attribute for the CSRF tag. |
1165
- | `rails_check_csrf!` | Verifies the authenticity token for the current request. |
1166
- | `rails_controller_instance` | Instance of the controller with the request env context. |
1167
- | `rails_controller` | Controller class to use for rendering and CSRF protection. |
1168
- | `rails_account_model` | Model class connected with the accounts table. |
951
+ The `configure` call loads the rodauth plugin. By convention, it receives an
952
+ auth class and configuration name as positional arguments (forwarded as
953
+ `:auth_class` and `:name` plugin options), a block for anonymous auth classes,
954
+ and also accepts any additional plugin options.
1169
955
 
1170
- The `Rodauth::Rails` module has a few config settings available as well:
956
+ ```rb
957
+ class RodauthApp < Rodauth::Rails::App
958
+ # named auth class
959
+ configure(RodauthMain)
960
+ configure(RodauthAdmin, :admin)
1171
961
 
1172
- | Name | Description |
1173
- | :----- | :---------- |
1174
- | `app` | Constant name of your Rodauth app, which is called by the middleware. |
1175
- | `middleware` | Whether to insert the middleware into the Rails application's middleware stack. Defaults to `true`. |
962
+ # anonymous auth class
963
+ configure { ... }
964
+ configure(:admin) { ... }
1176
965
 
1177
- ```rb
1178
- # config/initializers/rodauth.rb
1179
- Rodauth::Rails.configure do |config|
1180
- config.app = "RodauthApp"
1181
- config.middleware = true
966
+ # plugin options
967
+ configure(RodauthMain, json: :only)
1182
968
  end
1183
969
  ```
1184
970
 
1185
- For the list of configuration methods provided by Rodauth, see the [feature
1186
- documentation].
1187
-
1188
- ## Custom extensions
971
+ #### Route block
1189
972
 
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.
1193
-
1194
- Here is an example of an LDAP authentication extension that uses the
1195
- [simple_ldap_authenticator] gem.
973
+ The `route` block is called for each request, before it reaches the Rails
974
+ router, and it's yielded the request object.
1196
975
 
1197
976
  ```rb
1198
- # app/lib/rodauth_ldap.rb
1199
- module RodauthLdap
1200
- def require_bcrypt?
1201
- false
1202
- end
1203
-
1204
- def password_match?(password)
1205
- SimpleLdapAuthenticator.valid?(account[:email], password)
1206
- end
1207
- end
1208
- ```
1209
- ```rb
1210
- # app/lib/rodauth_app.rb
1211
977
  class RodauthApp < Rodauth::Rails::App
1212
- configure do
1213
- # ...
1214
- auth_class_eval do
1215
- include RodauthLdap
1216
- end
1217
- # ...
978
+ route do |r|
979
+ # called before each request
1218
980
  end
1219
981
  end
1220
982
  ```
1221
983
 
1222
- ## Testing
984
+ #### Routing prefix
1223
985
 
1224
- System (browser) tests for Rodauth actions could look something like this:
986
+ If you use a routing prefix, you don't need to add a call to `r.on` like with
987
+ vanilla Rodauth, as `r.rodauth` has been modified to automatically route the
988
+ prefix.
1225
989
 
1226
990
  ```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
1240
- end
1241
-
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
1247
-
1248
- login
1249
- assert_match "You have been logged in", page.text
1250
- end
1251
-
1252
- 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"
991
+ class RodauthApp < Rodauth::Rails::App
992
+ configure do
993
+ prefix "/user"
1276
994
  end
1277
995
 
1278
- def logout
1279
- visit "/logout"
1280
- click_on "Logout"
996
+ route do |r|
997
+ r.rodauth # no need to wrap with `r.on("user") { ... }`
1281
998
  end
1282
999
  end
1283
1000
  ```
1284
1001
 
1285
- While request tests in JSON API mode with JWT tokens could look something like
1286
- this:
1002
+ ### Auth class
1003
+
1004
+ The [`Rodauth::Rails::Auth`](/lib/rodauth/rails/auth.rb) class is a subclass of
1005
+ `Rodauth::Auth`, which preloads the `rails` rodauth feature, sets [HMAC] secret to
1006
+ Rails' secret key base, and modifies some [configuration defaults](#rodauth-defaults).
1287
1007
 
1288
1008
  ```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"]
1009
+ class RodauthMain < Rodauth::Rails::Auth
1010
+ configure do
1011
+ # authentication configuration
1301
1012
  end
1013
+ end
1014
+ ```
1302
1015
 
1303
- test "logging in and logging out" do
1304
- create_account(verify: true)
1016
+ ### Rodauth feature
1305
1017
 
1306
- logout
1307
- assert_response :success
1308
- assert_match "You have been logged out", JSON.parse(body)["success"]
1018
+ The [`rails`](/lib/rodauth/rails/feature.rb) Rodauth feature loaded by
1019
+ `Rodauth::Rails::Auth` provides the main part of the Rails integration for Rodauth:
1309
1020
 
1310
- login
1311
- assert_response :success
1312
- assert_match "You have been logged in", JSON.parse(body)["success"]
1313
- end
1021
+ * uses Action View for template rendering
1022
+ * uses Action Dispatch for CSRF protection
1023
+ * runs Action Controller callbacks and rescue from blocks around Rodauth requests
1024
+ * uses Action Mailer to create and deliver emails
1025
+ * uses Action Controller instrumentation around Rodauth requests
1026
+ * uses Action Mailer's default URL options when calling Rodauth outside of a request
1314
1027
 
1315
- private
1028
+ ### Controller
1316
1029
 
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
1030
+ The Rodauth app stores the `Rodauth::Rails::Auth` instances in the Rack env
1031
+ hash, which is then available in your Rails app:
1321
1032
 
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 }
1327
- end
1033
+ ```rb
1034
+ request.env["rodauth"] #=> #<RodauthMain>
1035
+ request.env["rodauth.admin"] #=> #<RodauthAdmin> (if using multiple configurations)
1036
+ ```
1328
1037
 
1329
- def login(email: "user@example.com", password: "secret")
1330
- post "/login", as: :json, params: { login: email, password: password }
1331
- end
1038
+ For convenience, this object can be accessed via the `#rodauth` method in views
1039
+ and controllers:
1332
1040
 
1333
- def logout
1334
- post "/logout", as: :json, headers: { "Authorization" => headers["Authorization"] }
1041
+ ```rb
1042
+ class MyController < ApplicationController
1043
+ def my_action
1044
+ rodauth #=> #<RodauthMain>
1045
+ rodauth(:admin) #=> #<RodauthAdmin> (if using multiple configurations)
1335
1046
  end
1336
1047
  end
1337
1048
  ```
1049
+ ```erb
1050
+ <% rodauth #=> #<RodauthMain> %>
1051
+ <% rodauth(:admin) #=> #<RodauthAdmin> (if using multiple configurations) %>
1052
+ ```
1338
1053
 
1339
- If you're delivering emails in the background, make sure to set Active Job
1340
- queue adapter to `:test` or `:inline`:
1054
+ ### Sequel
1341
1055
 
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
- ```
1056
+ Rodauth uses the [Sequel] library for database interaction, which offers
1057
+ powerful APIs for building advanced queries (it supports SQL expressions,
1058
+ database-agnostic date arithmetic, SQL function calls).
1059
+
1060
+ If you're using Active Record in your application, the `rodauth:install`
1061
+ generator automatically configures Sequel to reuse ActiveRecord's database
1062
+ connection, using the [sequel-activerecord_connection] gem.
1063
+
1064
+ This means that, from the usage perspective, Sequel can be considered just
1065
+ as an implementation detail of Rodauth.
1350
1066
 
1351
1067
  ## Rodauth defaults
1352
1068
 
@@ -1393,16 +1109,15 @@ end
1393
1109
 
1394
1110
  The recommended [Rodauth migration] stores possible account status values in a
1395
1111
  separate table, and creates a foreign key on the accounts table, which ensures
1396
- only a valid status value will be persisted.
1112
+ only a valid status value will be persisted. Unfortunately, this doesn't work
1113
+ when the database is restored from the schema file, in which case the account
1114
+ statuses table will be empty. This happens in tests by default, but it's also
1115
+ not unusual to do it in development.
1397
1116
 
1398
- Unfortunately, this doesn't work when the database is restored from the schema
1399
- file, in which case the account statuses table will be empty. This happens in
1400
- tests by default, but it's also commonly done in development.
1401
-
1402
- To address this, rodauth-rails modifies the setup to store account status text
1403
- directly in the accounts table. If you're worried about invalid status values
1404
- creeping in, you may use enums instead. Alternatively, you can always go back
1405
- to the setup recommended by Rodauth.
1117
+ To address this, rodauth-rails uses a `status` column without a separate table.
1118
+ If you're worried about invalid status values creeping in, you may use enums
1119
+ instead. Alternatively, you can always go back to the setup recommended by
1120
+ Rodauth.
1406
1121
 
1407
1122
  ```rb
1408
1123
  # in the migration:
@@ -1418,14 +1133,13 @@ create_table :accounts do |t|
1418
1133
  end
1419
1134
  ```
1420
1135
  ```diff
1421
- configure do
1422
- # ...
1423
- - account_status_column :status
1424
- - account_unverified_status_value "unverified"
1425
- - account_open_status_value "verified"
1426
- - account_closed_status_value "closed"
1427
- # ...
1428
- end
1136
+ class RodauthMain < Rodauth::Rails::Auth
1137
+ configure do
1138
+ # ...
1139
+ - account_status_column :status
1140
+ # ...
1141
+ end
1142
+ end
1429
1143
  ```
1430
1144
 
1431
1145
  ### Deadline values
@@ -1454,7 +1168,6 @@ conduct](https://github.com/janko/rodauth-rails/blob/master/CODE_OF_CONDUCT.md).
1454
1168
  [Rodauth]: https://github.com/jeremyevans/rodauth
1455
1169
  [Sequel]: https://github.com/jeremyevans/sequel
1456
1170
  [feature documentation]: http://rodauth.jeremyevans.net/documentation.html
1457
- [JWT gem]: https://github.com/jwt/ruby-jwt
1458
1171
  [Bootstrap]: https://getbootstrap.com/
1459
1172
  [Roda]: http://roda.jeremyevans.net/
1460
1173
  [HMAC]: http://rodauth.jeremyevans.net/rdoc/files/README_rdoc.html#label-HMAC
@@ -1463,7 +1176,6 @@ conduct](https://github.com/janko/rodauth-rails/blob/master/CODE_OF_CONDUCT.md).
1463
1176
  [sequel-activerecord_connection]: https://github.com/janko/sequel-activerecord_connection
1464
1177
  [plugin options]: http://rodauth.jeremyevans.net/rdoc/files/README_rdoc.html#label-Plugin+Options
1465
1178
  [hmac]: http://rodauth.jeremyevans.net/rdoc/files/README_rdoc.html#label-HMAC
1466
- [OmniAuth]: https://github.com/omniauth/omniauth
1467
1179
  [otp]: http://rodauth.jeremyevans.net/rdoc/files/doc/otp_rdoc.html
1468
1180
  [sms_codes]: http://rodauth.jeremyevans.net/rdoc/files/doc/sms_codes_rdoc.html
1469
1181
  [recovery_codes]: http://rodauth.jeremyevans.net/rdoc/files/doc/recovery_codes_rdoc.html
@@ -1484,3 +1196,6 @@ conduct](https://github.com/janko/rodauth-rails/blob/master/CODE_OF_CONDUCT.md).
1484
1196
  [internal_request]: http://rodauth.jeremyevans.net/rdoc/files/doc/internal_request_rdoc.html
1485
1197
  [composite_primary_keys]: https://github.com/composite-primary-keys/composite_primary_keys
1486
1198
  [path_class_methods]: https://rodauth.jeremyevans.net/rdoc/files/doc/path_class_methods_rdoc.html
1199
+ [account types]: https://github.com/janko/rodauth-rails/wiki/Account-Types
1200
+ [custom mailer worker]: https://github.com/janko/rodauth-rails/wiki/Custom-Mailer-Worker
1201
+ [Turbo]: https://turbo.hotwired.dev/