rodauth-rails 0.8.0 → 0.10.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0f1312dbd1bb4dc0d954c77a5ff350b5c9e1ff3fc4dd45b8834cd3e7d0280a22
4
- data.tar.gz: 5dda5720126361589a428add9b8256b35aa53644166ca7a8a6d14c5baef53f02
3
+ metadata.gz: 4cc138f57505a1bbf92267e02b8d9b87f50c0dd9b6c114891f649c0b15878637
4
+ data.tar.gz: 28fc8264a8629dd186a446a4d067167d572f8ea58b65c742f12f81a5192221db
5
5
  SHA512:
6
- metadata.gz: f70e5a44db25c016fe92169be342d1f489cd0e3307fe6c06dbe822c28c05f55dc696b26721836d315daabbbfb0889d18357cec3bb7aa52932649f5ecb08ceedb
7
- data.tar.gz: 6af3cd43f9266729049d984c9da58beff019dd2f0148465d65c8f814602d9a9678308d752ef469f08ab381a1373b919d1e61b62b204751d95ca57d10ed05de2a
6
+ metadata.gz: 74645990b10677d44503f272a63300465881c6e596b514ce0e1d1607689d8ace60c5e70a79c952256ad73bf704a8d268e27af3da8cdb617c5b381f752b302c4b
7
+ data.tar.gz: 1525c4a51d4323e348ee2dc117e5ef320214f42f385549f5646af1d8e1792bfeac2be3f220b473873aed8fc72f4448aade9848b82fdd2c348874f8b4950e4631
data/CHANGELOG.md CHANGED
@@ -1,3 +1,49 @@
1
+ ## 0.10.0 (2021-03-23)
2
+
3
+ * Add `Rodauth::Rails::Auth` superclass for moving configurations into separate files (@janko)
4
+
5
+ * Load the `pass` Roda plugin and recommend calling `r.pass` on prefixed routes (@janko)
6
+
7
+ * Improve Roda middleware inspect output (@janko)
8
+
9
+ * Create `RodauthMailer` and email templates in `rodauth:install`, and remove `rodauth:mailer` (@janko)
10
+
11
+ * Raise `KeyError` in `#rodauth` method when the Rodauth instance doesn't exist (@janko)
12
+
13
+ * Add `Rodauth::Rails.authenticated` routing constraint for requiring authentication (@janko)
14
+
15
+ ## 0.9.1 (2021-02-10)
16
+
17
+ * Fix flash integration being loaded for API-only apps and causing an error (@dmitryzuev)
18
+
19
+ * Change account status column default to `unverified` in migration to match Rodauth's default (@basabin54)
20
+
21
+ ## 0.9.0 (2021-02-07)
22
+
23
+ * Load Roda's JSON support by default, so that enabling `json`/`jwt` feature is all that's needed (@janko)
24
+
25
+ * Bump Rodauth dependency to 2.9+ (@janko)
26
+
27
+ * Add `--json` option for `rodauth:install` generator for configuring `json` feature (@janko)
28
+
29
+ * Add `--jwt` option for `rodauth:install` generator for configuring `jwt` feature (@janko)
30
+
31
+ * Remove the `--api` option from `rodauth:install` generator (@janko)
32
+
33
+ ## 0.8.2 (2021-01-10)
34
+
35
+ * Reset Rails session on `#clear_session`, protecting from potential session fixation attacks (@janko)
36
+
37
+ ## 0.8.1 (2021-01-04)
38
+
39
+ * Fix blank email body when `json: true` and `ActionController::API` descendant are used (@janko)
40
+
41
+ * Make view and email rendering work when there are multiple configurations and one is `json: :only` (@janko)
42
+
43
+ * Don't attempt to protect against forgery when `ActionController::API` descendant is used (@janko)
44
+
45
+ * Mark content of rodauth built-in partials as HTML-safe (@janko)
46
+
1
47
  ## 0.8.0 (2021-01-03)
2
48
 
3
49
  * Add `--api` option to `rodauth:install` generator for choosing JSON-only configuration (@janko)
data/README.md CHANGED
@@ -2,6 +2,35 @@
2
2
 
3
3
  Provides Rails integration for the [Rodauth] authentication framework.
4
4
 
5
+ ## Table of contents
6
+
7
+ * [Resources](#resources)
8
+ * [Why Rodauth?](#why-rodauth)
9
+ * [Upgrading](#upgrading)
10
+ * [Installation](#installation)
11
+ * [Usage](#usage)
12
+ - [Routes](#routes)
13
+ - [Current account](#current-account)
14
+ - [Requiring authentication](#requiring-authentication)
15
+ - [Views](#views)
16
+ - [Mailer](#mailer)
17
+ - [Migrations](#migrations)
18
+ - [Multiple configurations](#multiple-configurations)
19
+ - [Calling controller methods](#calling-controller-methods)
20
+ - [Rodauth instance](#rodauth-instance)
21
+ * [How it works](#how-it-works)
22
+ - [Middleware](#middleware)
23
+ - [App](#app)
24
+ - [Sequel](#sequel)
25
+ * [JSON API](#json-api)
26
+ * [OmniAuth](#omniauth)
27
+ * [Configuring](#configuring)
28
+ * [Custom extensions](#custom-extensions)
29
+ * [Testing](#testing)
30
+ * [Rodauth defaults](#rodauth-defaults)
31
+ - [Database functions](#database-functions)
32
+ - [Account statuses](#account-statuses)
33
+
5
34
  ## Resources
6
35
 
7
36
  Useful links:
@@ -18,11 +47,11 @@ Articles:
18
47
  ## Why Rodauth?
19
48
 
20
49
  There are already several popular authentication solutions for Rails (Devise,
21
- Sorcery, Clearance, Authlogic), so why would you choose Rodauth? Well, because
22
- it has many advantages over the mentioned alternatives:
50
+ Sorcery, Clearance, Authlogic), so why would you choose Rodauth? Here are some
51
+ of the advantages that stand out for me:
23
52
 
24
53
  * multifactor authentication ([TOTP][otp], [SMS codes][sms_codes], [recovery codes][recovery_codes], [WebAuthn][webauthn])
25
- * standardized [JSON API support][jwt] (for every feature)
54
+ * standardized [JSON API support][json] for every feature (including [JWT][jwt])
26
55
  * enterprise security features ([password complexity][password_complexity], [disallow password reuse][disallow_password_reuse], [password expiration][password_expiration], [session expiration][session_expiration], [single session][single_session], [account expiration][account_expiration])
27
56
  * [email authentication][email_auth] (aka "passwordless")
28
57
  * [audit logging][audit_logging] (for any action)
@@ -54,7 +83,7 @@ documentation][hmac] for instructions on how to safely transition, or just set
54
83
  Add the gem to your Gemfile:
55
84
 
56
85
  ```rb
57
- gem "rodauth-rails", "~> 0.6"
86
+ gem "rodauth-rails", "~> 0.10"
58
87
 
59
88
  # gem "jwt", require: false # for JWT feature
60
89
  # gem "rotp", require: false # for OTP feature
@@ -73,7 +102,9 @@ $ rails generate rodauth:install
73
102
  Or if you want Rodauth endpoints to be exposed via JSON API:
74
103
 
75
104
  ```sh
76
- $ rails generate rodauth:install --api
105
+ $ rails generate rodauth:install --json # regular authentication using the Rails session
106
+ # or
107
+ $ rails generate rodauth:install --jwt # token authentication via the "Authorization" header
77
108
  $ bundle add jwt
78
109
  ```
79
110
 
@@ -85,6 +116,7 @@ The generator will create the following files:
85
116
  * Rodauth app at `app/lib/rodauth_app.rb`
86
117
  * Rodauth controller at `app/controllers/rodauth_controller.rb`
87
118
  * Account model at `app/models/account.rb`
119
+ * Rodauth mailer at `app/mailers/rodauth_mailer.rb` with views
88
120
 
89
121
  ### Migration
90
122
 
@@ -180,6 +212,23 @@ class Account < ApplicationRecord
180
212
  end
181
213
  ```
182
214
 
215
+ ### Rodauth mailer
216
+
217
+ The default Rodauth app is configured to use `RodauthMailer` mailer
218
+ for sending authentication emails.
219
+
220
+ ```rb
221
+ # app/mailers/rodauth_mailer.rb
222
+ class RodauthMailer < ApplicationMailer
223
+ def verify_account(recipient, email_link) ... end
224
+ def reset_password(recipient, email_link) ... end
225
+ def verify_login_change(recipient, old_login, new_login, email_link) ... end
226
+ def password_changed(recipient) ... end
227
+ # def email_auth(recipient, email_link) ... end
228
+ # def unlock_account(recipient, email_link) ... end
229
+ end
230
+ ```
231
+
183
232
  ## Usage
184
233
 
185
234
  ### Routes
@@ -241,7 +290,7 @@ class ApplicationController < ActionController::Base
241
290
  rodauth.logout
242
291
  rodauth.login_required
243
292
  end
244
- helper_method :current_account
293
+ helper_method :current_account # skip if inheriting from ActionController:API
245
294
  end
246
295
  ```
247
296
 
@@ -299,15 +348,52 @@ class PostsController < ApplicationController
299
348
  end
300
349
  ```
301
350
 
302
- Or at the Rails router level:
351
+ #### Routing constraints
352
+
353
+ You can also require authentication at the Rails router level by
354
+ using a built-in `authenticated` routing constraint:
303
355
 
304
356
  ```rb
305
357
  # config/routes.rb
306
358
  Rails.application.routes.draw do
307
- constraints -> (r) { r.env["rodauth"].require_authentication } do
308
- namespace :admin do
309
- # ...
310
- end
359
+ constraints Rodauth::Rails.authenticated do
360
+ # ... authenticated routes ...
361
+ end
362
+ end
363
+ ```
364
+
365
+ If you want additional conditions, you can pass in a block, which is
366
+ called with the Rodauth instance:
367
+
368
+ ```rb
369
+ # config/routes.rb
370
+ Rails.application.routes.draw do
371
+ # require multifactor authentication to be setup
372
+ constraints Rodauth::Rails.authenticated { |rodauth| rodauth.uses_two_factor_authentication? } do
373
+ # ...
374
+ end
375
+ end
376
+ ```
377
+
378
+ You can specify the Rodauth configuration by passing the configuration name:
379
+
380
+ ```rb
381
+ # config/routes.rb
382
+ Rails.application.routes.draw do
383
+ constraints Rodauth::Rails.authenticated(:admin) do
384
+ # ...
385
+ end
386
+ end
387
+ ```
388
+
389
+ If you need something more custom, you can always create the routing constraint
390
+ manually:
391
+
392
+ ```rb
393
+ # config/routes.rb
394
+ Rails.application.routes.draw do
395
+ constraints -> (r) { !r.env["rodauth"].logged_in? } do # or "rodauth.admin"
396
+ # routes when the user is not logged in
311
397
  end
312
398
  end
313
399
  ```
@@ -375,54 +461,33 @@ end
375
461
 
376
462
  ### Mailer
377
463
 
378
- Depending on the features you've enabled, Rodauth may send emails as part of
379
- the authentication flow. Most email settings can be customized:
464
+ The install generator will create `RodauthMailer` with default email templates,
465
+ and configure Rodauth features that send emails as part of the authentication
466
+ flow to use it.
380
467
 
381
468
  ```rb
382
- # app/lib/rodauth_app.rb
383
- class RodauthApp < Rodauth::Rails::App
384
- # ...
385
- configure do
469
+ # app/mailers/rodauth_mailer.rb
470
+ class RodauthMailer < ApplicationMailer
471
+ def verify_account(recipient, email_link)
386
472
  # ...
387
- # general settings
388
- email_from "no-reply@myapp.com"
389
- email_subject_prefix "[MyApp] "
390
- send_email(&:deliver_later)
473
+ end
474
+ def reset_password(recipient, email_link)
391
475
  # ...
392
- # feature settings
393
- verify_account_email_subject "Verify your account"
394
- verify_account_email_body { "Verify your account by visting this link: #{verify_account_email_link}" }
476
+ end
477
+ def verify_login_change(recipient, old_login, new_login, email_link)
395
478
  # ...
396
479
  end
480
+ def password_changed(recipient)
481
+ # ...
482
+ end
483
+ # def email_auth(recipient, email_link)
484
+ # ...
485
+ # end
486
+ # def unlock_account(recipient, email_link)
487
+ # ...
488
+ # end
397
489
  end
398
490
  ```
399
-
400
- This is convenient when starting out, but eventually you might want to use your
401
- own mailer. You can start by running the following command:
402
-
403
- ```sh
404
- $ rails generate rodauth:mailer
405
- ```
406
-
407
- This will create a `RodauthMailer` with the associated mailer views in
408
- `app/views/rodauth_mailer` directory:
409
-
410
- ```rb
411
- # app/mailers/rodauth_mailer.rb
412
- class RodauthMailer < ApplicationMailer
413
- def verify_account(recipient, email_link) ... end
414
- def reset_password(recipient, email_link) ... end
415
- def verify_login_change(recipient, old_login, new_login, email_link) ... end
416
- def password_changed(recipient) ... end
417
- # def email_auth(recipient, email_link) ... end
418
- # def unlock_account(recipient, email_link) ... end
419
- end
420
- ```
421
-
422
- You can then uncomment the lines in your Rodauth configuration to have it call
423
- your mailer. If you've enabled additional authentication features that send
424
- emails, make sure to override their `create_*_email` methods as well.
425
-
426
491
  ```rb
427
492
  # app/lib/rodauth_app.rb
428
493
  class RodauthApp < Rodauth::Rails::App
@@ -456,10 +521,17 @@ class RodauthApp < Rodauth::Rails::App
456
521
  end
457
522
  ```
458
523
 
459
- This approach can be used even if you're using a 3rd-party service for
460
- transactional emails, where emails are sent via HTTP instead of SMTP. Whatever
461
- the `create_*_email` block returns will be passed to `send_email`, so you can
462
- be creative.
524
+ The above configuration uses `#deliver_later`, which assumes Active Job is
525
+ configured. It's generally recommended to send emails in a background job,
526
+ for better throughput and ability to retry. However, if you want to send emails
527
+ synchronously, you can modify the code to call `#deliver` instead.
528
+
529
+ The `#send_email` method will receive whatever object is returned by the
530
+ `#create_*_email` methods. But if that doesn't suit you, you can override
531
+ `#send_*_email` methods instead, which are expected to send the email
532
+ immediately. This might work better in scenarios such as using a 3rd-party
533
+ service for transactional emails, where emails are sent via HTTP instead of
534
+ SMTP.
463
535
 
464
536
  ### Migrations
465
537
 
@@ -481,6 +553,142 @@ class CreateRodauthOtpSmsCodesRecoveryCodes < ActiveRecord::Migration
481
553
  end
482
554
  ```
483
555
 
556
+ ### Multiple configurations
557
+
558
+ If you need to handle multiple types of accounts that require different
559
+ authentication logic, you can create different configurations for them:
560
+
561
+ ```rb
562
+ # app/lib/rodauth_app.rb
563
+ class RodauthApp < Rodauth::Rails::App
564
+ # primary configuration
565
+ configure do
566
+ # ...
567
+ end
568
+
569
+ # alternative configuration
570
+ configure(:admin) do
571
+ # ... enable features ...
572
+ prefix "/admin"
573
+ session_key_prefix "admin_"
574
+ remember_cookie_key "_admin_remember" # if using remember feature
575
+
576
+ # if you want separate tables
577
+ accounts_table :admin_accounts
578
+ password_hash_table :admin_account_password_hashes
579
+ # ...
580
+ end
581
+
582
+ route do |r|
583
+ r.rodauth
584
+
585
+ r.on "admin" do
586
+ r.rodauth(:admin)
587
+ r.pass # allow the Rails app to handle other "/admin/*" requests
588
+ end
589
+ # ...
590
+ end
591
+ end
592
+ ```
593
+
594
+ Then in your application you can reference the secondary Rodauth instance:
595
+
596
+ ```rb
597
+ rodauth(:admin).login_path #=> "/admin/login"
598
+ ```
599
+
600
+ #### Named auth classes
601
+
602
+ A `configure` block inside `Rodauth::Rails::App` will internally create an
603
+ anonymous `Rodauth::Auth` subclass, and register it under the given name.
604
+ However, you can also define the auth classes explicitly, by creating
605
+ subclasses of `Rodauth::Rails::Auth`:
606
+
607
+ ```rb
608
+ # app/lib/rodauth_main.rb
609
+ class RodauthMain < Rodauth::Rails::Auth
610
+ configure do
611
+ # ... main configuration ...
612
+ end
613
+ end
614
+ ```
615
+ ```rb
616
+ # app/lib/rodauth_admin.rb
617
+ class RodauthAdmin < Rodauth::Rails::Auth
618
+ configure do
619
+ # ...
620
+ prefix "/admin"
621
+ session_key_prefix "admin_"
622
+ # ...
623
+ end
624
+ end
625
+ ```
626
+ ```rb
627
+ # app/lib/rodauth_app.rb
628
+ class RodauthApp < Rodauth::Rails::App
629
+ configure RodauthMain
630
+ configure RodauthAdmin, :admin
631
+ # ...
632
+ end
633
+ ```
634
+
635
+ This allows having each configuration in a dedicated file, and named constants
636
+ improve introspection and error messages. You can also use inheritance to share
637
+ common settings:
638
+
639
+ ```rb
640
+ # app/lib/rodauth_base.rb
641
+ class RodauthBase < Rodauth::Rails::App
642
+ # common settings that can be shared between multiple configurations
643
+ configure do
644
+ enable :login, :logout
645
+ login_return_to_requested_location? true
646
+ logout_redirect "/"
647
+ # ...
648
+ end
649
+ end
650
+ ```
651
+ ```rb
652
+ # app/lib/rodauth_main.rb
653
+ class RodauthMain < RodauthBase # inherit common settings
654
+ configure do
655
+ # ... customize main ...
656
+ end
657
+ end
658
+ ```
659
+ ```rb
660
+ # app/lib/rodauth_admin.rb
661
+ class RodauthAdmin < RodauthBase # inherit common settings
662
+ configure do
663
+ # ... customize admin ...
664
+ end
665
+ end
666
+ ```
667
+
668
+ Another benefit is that you can define custom methods directly on the class
669
+ instead of through `auth_class_eval`:
670
+
671
+ ```rb
672
+ # app/lib/rodauth_admin.rb
673
+ class RodauthAdmin < Rodauth::Rails::Auth
674
+ configure do
675
+ # ...
676
+ end
677
+
678
+ def superadmin?
679
+ Role.where(account_id: session_id).any? { |role| role.name == "superadmin" }
680
+ end
681
+ end
682
+ ```
683
+ ```rb
684
+ # config/routes.rb
685
+ Rails.application.routes.draw do
686
+ constraints Rodauth::Rails.authenticated(:admin) { |rodauth| rodauth.superadmin? } do
687
+ mount Sidekiq::Web => "sidekiq"
688
+ end
689
+ end
690
+ ```
691
+
484
692
  ### Calling controller methods
485
693
 
486
694
  When using Rodauth before/after hooks or generally overriding your Rodauth
@@ -514,7 +722,7 @@ Rodauth operations outside of the request context. rodauth-rails gives you the
514
722
  ability to retrieve the Rodauth instance:
515
723
 
516
724
  ```rb
517
- rodauth = Rodauth::Rails.rodauth # or Rodauth::Rails.rodauth(:secondary)
725
+ rodauth = Rodauth::Rails.rodauth # or Rodauth::Rails.rodauth(:admin)
518
726
 
519
727
  rodauth.login_url #=> "https://example.com/login"
520
728
  rodauth.account_from_login("user@example.com") # loads user by email
@@ -545,8 +753,8 @@ The Rodauth app stores the `Rodauth::Auth` instance in the Rack env hash, which
545
753
  is then available in your Rails app:
546
754
 
547
755
  ```rb
548
- request.env["rodauth"] #=> #<Rodauth::Auth>
549
- request.env["rodauth.secondary"] #=> #<Rodauth::Auth> (if using multiple configurations)
756
+ request.env["rodauth"] #=> #<Rodauth::Auth>
757
+ request.env["rodauth.admin"] #=> #<Rodauth::Auth> (if using multiple configurations)
550
758
  ```
551
759
 
552
760
  For convenience, this object can be accessed via the `#rodauth` method in views
@@ -555,14 +763,14 @@ and controllers:
555
763
  ```rb
556
764
  class MyController < ApplicationController
557
765
  def my_action
558
- rodauth #=> #<Rodauth::Auth>
559
- rodauth(:secondary) #=> #<Rodauth::Auth> (if using multiple configurations)
766
+ rodauth #=> #<Rodauth::Auth>
767
+ rodauth(:admin) #=> #<Rodauth::Auth> (if using multiple configurations)
560
768
  end
561
769
  end
562
770
  ```
563
771
  ```erb
564
- <% rodauth #=> #<Rodauth::Auth> %>
565
- <% rodauth(:secondary) #=> #<Rodauth::Auth> (if using multiple configurations) %>
772
+ <% rodauth #=> #<Rodauth::Auth> %>
773
+ <% rodauth(:admin) #=> #<Rodauth::Auth> (if using multiple configurations) %>
566
774
  ```
567
775
 
568
776
  ### App
@@ -584,7 +792,7 @@ any additional [plugin options].
584
792
  class RodauthApp < Rodauth::Rails::App
585
793
  configure { ... } # defining default Rodauth configuration
586
794
  configure(json: true) { ... } # passing options to the Rodauth plugin
587
- configure(:secondary) { ... } # defining multiple Rodauth configurations
795
+ configure(:admin) { ... } # defining multiple Rodauth configurations
588
796
  end
589
797
  ```
590
798
 
@@ -619,15 +827,32 @@ function calls).
619
827
 
620
828
  If ActiveRecord is used in the application, the `rodauth:install` generator
621
829
  will have automatically configured Sequel to reuse ActiveRecord's database
622
- connection (using the [sequel-activerecord_connection] gem).
830
+ connection, using the [sequel-activerecord_connection] gem.
623
831
 
624
832
  This means that, from the usage perspective, Sequel can be considered just
625
833
  as an implementation detail of Rodauth.
626
834
 
627
835
  ## JSON API
628
836
 
629
- JSON API support in Rodauth is provided by the [JWT feature][jwt]. You'll need
630
- to install the [JWT gem], enable JSON support and enable the JWT feature:
837
+ To make Rodauth endpoints accessible via JSON API, enable the [`json`][json]
838
+ feature:
839
+
840
+ ```rb
841
+ # app/lib/rodauth_app.rb
842
+ class RodauthApp < Rodauth::Rails::App
843
+ configure do
844
+ # ...
845
+ enable :json
846
+ only_json? true # accept only JSON requests
847
+ # ...
848
+ end
849
+ end
850
+ ```
851
+
852
+ This will store account session data into the Rails session. If you rather want
853
+ stateless token-based authentication via the `Authorization` header, enable the
854
+ [`jwt`][jwt] feature (which builds on top of the `json` feature) and add the
855
+ [JWT gem] to the Gemfile:
631
856
 
632
857
  ```sh
633
858
  $ bundle add jwt
@@ -635,23 +860,33 @@ $ bundle add jwt
635
860
  ```rb
636
861
  # app/lib/rodauth_app.rb
637
862
  class RodauthApp < Rodauth::Rails::App
638
- configure(json: :only) do
863
+ configure do
639
864
  # ...
640
865
  enable :jwt
641
- # make sure to store the JWT secret below in a safe place
642
- jwt_secret "...your secret key..."
866
+ jwt_secret "<YOUR_SECRET_KEY>" # store the JWT secret in a safe place
867
+ only_json? true # accept only JSON requests
643
868
  # ...
644
869
  end
645
870
  end
646
871
  ```
647
872
 
648
- With the above configuration, Rodauth routes will only be accessible via JSON
649
- requests. If you still want to allow HTML access alongside JSON, change `json:
650
- :only` to `json: true`.
873
+ If you need Cross-Origin Resource Sharing and/or JWT refresh tokens, enable the
874
+ corresponding Rodauth features and create the necessary tables:
651
875
 
652
- Emails will automatically work in JSON-only mode, because `Rodauth::Rails::App`
653
- comes with Roda's `render` plugin loaded. They are customized the same as in
654
- the non-JSON case.
876
+ ```sh
877
+ $ rails generate rodauth:migration jwt_refresh
878
+ $ rails db:migrate
879
+ ```
880
+ ```rb
881
+ # app/lib/rodauth_app.rb
882
+ class RodauthApp < Rodauth::Rails::App
883
+ configure do
884
+ # ...
885
+ enable :jwt, :jwt_cors, :jwt_refresh
886
+ # ...
887
+ end
888
+ end
889
+ ```
655
890
 
656
891
  ## OmniAuth
657
892
 
@@ -745,7 +980,7 @@ class RodauthController < ApplicationController
745
980
 
746
981
  # create new account if it doesn't exist
747
982
  unless account
748
- account = Account.create!(email: auth["info"]["email"])
983
+ account = Account.create!(email: auth["info"]["email"], status: rodauth.account_open_status_value)
749
984
  end
750
985
 
751
986
  # create new identity if it doesn't exist
@@ -797,17 +1032,19 @@ end
797
1032
 
798
1033
  When developing custom extensions for Rodauth inside your Rails project, it's
799
1034
  better to use plain modules (at least in the beginning), because Rodauth
800
- feature API doesn't yet support Zeitwerk reloading well.
1035
+ feature design doesn't yet support Zeitwerk reloading well. Here is
1036
+ an example of an LDAP authentication extension that uses the
1037
+ [simple_ldap_authenticator] gem.
801
1038
 
802
1039
  ```rb
803
- # app/lib/rodauth_argon2.rb
804
- module RodauthArgon2
805
- def password_hash(password)
806
- Argon2::Password.create(password, t_cost: password_hash_cost, m_cost: password_hash_cost)
1040
+ # app/lib/rodauth_ldap.rb
1041
+ module RodauthLdap
1042
+ def require_bcrypt?
1043
+ false
807
1044
  end
808
1045
 
809
- def password_hash_match?(hash, password)
810
- Argon2::Password.verify_password(password, hash)
1046
+ def password_match?(password)
1047
+ SimpleLdapAuthenticator.valid?(account[:email], password)
811
1048
  end
812
1049
  end
813
1050
  ```
@@ -817,7 +1054,7 @@ class RodauthApp < Rodauth::Rails::App
817
1054
  configure do
818
1055
  # ...
819
1056
  auth_class_eval do
820
- include RodauthArgon2
1057
+ include RodauthLdap
821
1058
  end
822
1059
  # ...
823
1060
  end
@@ -826,48 +1063,147 @@ end
826
1063
 
827
1064
  ## Testing
828
1065
 
829
- If you're writing system tests, it's generally better to go through the actual
830
- authentication flow with tools like Capybara, and to not use any stubbing.
831
-
832
- In functional and integration tests you can just make requests to Rodauth
833
- routes:
1066
+ System (browser) tests for Rodauth actions could look something like this:
834
1067
 
835
1068
  ```rb
836
- # test/controllers/posts_controller_test.rb
837
- class PostsControllerTest < ActionDispatch::IntegrationTest
838
- test "should require authentication" do
839
- get posts_url
840
- assert_redirected_to "/login"
1069
+ # test/system/authentication_test.rb
1070
+ require "test_helper"
1071
+
1072
+ class AuthenticationTest < ActionDispatch::SystemTestCase
1073
+ include ActiveJob::TestHelper
1074
+ driven_by :rack_test
1075
+
1076
+ test "creating and verifying an account" do
1077
+ create_account
1078
+ assert_match "An email has been sent to you with a link to verify your account", page.text
1079
+
1080
+ verify_account
1081
+ assert_match "Your account has been verified", page.text
1082
+ end
1083
+
1084
+ test "logging in and logging out" do
1085
+ create_account(verify: true)
1086
+
1087
+ logout
1088
+ assert_match "You have been logged out", page.text
841
1089
 
842
1090
  login
843
- get posts_url
1091
+ assert_match "You have been logged in", page.text
1092
+ end
1093
+
1094
+ private
1095
+
1096
+ def create_account(email: "user@example.com", password: "secret", verify: false)
1097
+ visit "/create-account"
1098
+ fill_in "Login", with: email
1099
+ fill_in "Password", with: password
1100
+ fill_in "Confirm Password", with: password
1101
+ click_on "Create Account"
1102
+ verify_account if verify
1103
+ end
1104
+
1105
+ def verify_account
1106
+ perform_enqueued_jobs # run enqueued email deliveries
1107
+ email = ActionMailer::Base.deliveries.last
1108
+ verify_account_link = email.body.to_s[/\S+verify-account\S+/]
1109
+ visit verify_account_link
1110
+ click_on "Verify Account"
1111
+ end
1112
+
1113
+ def login(email: "user@example.com", password: "secret")
1114
+ visit "/login"
1115
+ fill_in "Login", with: email
1116
+ fill_in "Password", with: password
1117
+ click_on "Login"
1118
+ end
1119
+
1120
+ def logout
1121
+ visit "/logout"
1122
+ click_on "Logout"
1123
+ end
1124
+ end
1125
+ ```
1126
+
1127
+ While request tests in JSON API mode with JWT tokens could look something like
1128
+ this:
1129
+
1130
+ ```rb
1131
+ # test/integration/authentication_test.rb
1132
+ require "test_helper"
1133
+
1134
+ class AuthenticationTest < ActionDispatch::IntegrationTest
1135
+ test "creating and verifying an account" do
1136
+ create_account
1137
+ assert_response :success
1138
+ assert_match "An email has been sent to you with a link to verify your account", JSON.parse(body)["success"]
1139
+
1140
+ verify_account
844
1141
  assert_response :success
1142
+ assert_match "Your account has been verified", JSON.parse(body)["success"]
1143
+ end
1144
+
1145
+ test "logging in and logging out" do
1146
+ create_account(verify: true)
845
1147
 
846
1148
  logout
847
- assert_redirected_to "/login"
1149
+ assert_response :success
1150
+ assert_match "You have been logged out", JSON.parse(body)["success"]
1151
+
1152
+ login
1153
+ assert_response :success
1154
+ assert_match "You have been logged in", JSON.parse(body)["success"]
848
1155
  end
849
1156
 
850
1157
  private
851
1158
 
852
- def login(login: "user@example.com", password: "secret")
853
- post "/create-account", params: {
854
- "login" => login,
855
- "password" => password,
856
- "password-confirm" => password,
857
- }
1159
+ def create_account(email: "user@example.com", password: "secret", verify: false)
1160
+ post "/create-account", as: :json, params: { login: email, password: password, "password-confirm": password }
1161
+ verify_account if verify
1162
+ end
1163
+
1164
+ def verify_account
1165
+ perform_enqueued_jobs # run enqueued email deliveries
1166
+ email = ActionMailer::Base.deliveries.last
1167
+ verify_account_key = email.body.to_s[/verify-account\?key=(\S+)/, 1]
1168
+ post "/verify-account", as: :json, params: { key: verify_account_key }
1169
+ end
858
1170
 
859
- post "/login", params: {
860
- "login" => login,
861
- "password" => password,
862
- }
1171
+ def login(email: "user@example.com", password: "secret")
1172
+ post "/login", as: :json, params: { login: email, password: password }
863
1173
  end
864
1174
 
865
1175
  def logout
866
- post "/logout"
1176
+ post "/logout", as: :json, headers: { "Authorization" => headers["Authorization"] }
867
1177
  end
868
1178
  end
869
1179
  ```
870
1180
 
1181
+ If you're delivering emails in the background, make sure to set Active Job
1182
+ queue adapter to `:test`:
1183
+
1184
+ ```rb
1185
+ # config/environments/test.rb
1186
+ Rails.application.configure do |config|
1187
+ # ...
1188
+ config.active_job.queue_adapter = :test
1189
+ # ...
1190
+ end
1191
+ ```
1192
+
1193
+ If you need to create the account manually, you can do it as follows:
1194
+
1195
+ ```rb
1196
+ account_id = DB[:accounts].insert(
1197
+ email: "user@example.com",
1198
+ status: "verified",
1199
+ )
1200
+
1201
+ DB[:account_password_hashes].insert(
1202
+ account_id: account_id,
1203
+ password_hash: BCrypt::Password.create("secret", cost: BCrpyt::Engine::MIN_COST),
1204
+ )
1205
+ ```
1206
+
871
1207
  ## Rodauth defaults
872
1208
 
873
1209
  rodauth-rails changes some of the default Rodauth settings for easier setup:
@@ -976,6 +1312,7 @@ conduct](https://github.com/janko/rodauth-rails/blob/master/CODE_OF_CONDUCT.md).
976
1312
  [sms_codes]: http://rodauth.jeremyevans.net/rdoc/files/doc/sms_codes_rdoc.html
977
1313
  [recovery_codes]: http://rodauth.jeremyevans.net/rdoc/files/doc/recovery_codes_rdoc.html
978
1314
  [webauthn]: http://rodauth.jeremyevans.net/rdoc/files/doc/webauthn_rdoc.html
1315
+ [json]: http://rodauth.jeremyevans.net/rdoc/files/doc/json_rdoc.html
979
1316
  [jwt]: http://rodauth.jeremyevans.net/rdoc/files/doc/jwt_rdoc.html
980
1317
  [email_auth]: http://rodauth.jeremyevans.net/rdoc/files/doc/email_auth_rdoc.html
981
1318
  [audit_logging]: http://rodauth.jeremyevans.net/rdoc/files/doc/audit_logging_rdoc.html
@@ -987,3 +1324,4 @@ conduct](https://github.com/janko/rodauth-rails/blob/master/CODE_OF_CONDUCT.md).
987
1324
  [session_expiration]: http://rodauth.jeremyevans.net/rdoc/files/doc/session_expiration_rdoc.html
988
1325
  [single_session]: http://rodauth.jeremyevans.net/rdoc/files/doc/single_session_rdoc.html
989
1326
  [account_expiration]: http://rodauth.jeremyevans.net/rdoc/files/doc/account_expiration_rdoc.html
1327
+ [simple_ldap_authenticator]: https://github.com/jeremyevans/simple_ldap_authenticator