rodauth-rails 0.8.0 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +46 -0
- data/README.md +445 -107
- data/lib/generators/rodauth/install_generator.rb +26 -15
- data/lib/generators/rodauth/migration/base.erb +2 -2
- data/lib/generators/rodauth/templates/app/lib/rodauth_app.rb +50 -49
- data/lib/generators/rodauth/templates/app/mailers/rodauth_mailer.rb +3 -3
- data/lib/rodauth/rails.rb +20 -0
- data/lib/rodauth/rails/app.rb +15 -25
- data/lib/rodauth/rails/app/flash.rb +5 -3
- data/lib/rodauth/rails/app/middleware.rb +20 -10
- data/lib/rodauth/rails/auth.rb +40 -0
- data/lib/rodauth/rails/controller_methods.rb +1 -5
- data/lib/rodauth/rails/feature.rb +32 -14
- data/lib/rodauth/rails/tasks.rake +1 -1
- data/lib/rodauth/rails/version.rb +1 -1
- data/rodauth-rails.gemspec +3 -1
- metadata +23 -9
- data/lib/generators/rodauth/mailer_generator.rb +0 -37
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4cc138f57505a1bbf92267e02b8d9b87f50c0dd9b6c114891f649c0b15878637
|
4
|
+
data.tar.gz: 28fc8264a8629dd186a446a4d067167d572f8ea58b65c742f12f81a5192221db
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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?
|
22
|
-
|
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][
|
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.
|
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 --
|
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
|
-
|
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
|
308
|
-
|
309
|
-
|
310
|
-
|
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
|
-
|
379
|
-
|
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/
|
383
|
-
class
|
384
|
-
|
385
|
-
configure do
|
469
|
+
# app/mailers/rodauth_mailer.rb
|
470
|
+
class RodauthMailer < ApplicationMailer
|
471
|
+
def verify_account(recipient, email_link)
|
386
472
|
# ...
|
387
|
-
|
388
|
-
|
389
|
-
email_subject_prefix "[MyApp] "
|
390
|
-
send_email(&:deliver_later)
|
473
|
+
end
|
474
|
+
def reset_password(recipient, email_link)
|
391
475
|
# ...
|
392
|
-
|
393
|
-
|
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
|
-
|
460
|
-
|
461
|
-
|
462
|
-
|
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(:
|
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"]
|
549
|
-
request.env["rodauth.
|
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
|
559
|
-
rodauth(:
|
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
|
565
|
-
<% rodauth(:
|
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(:
|
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
|
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
|
-
|
630
|
-
|
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
|
863
|
+
configure do
|
639
864
|
# ...
|
640
865
|
enable :jwt
|
641
|
-
|
642
|
-
|
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
|
-
|
649
|
-
|
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
|
-
|
653
|
-
|
654
|
-
|
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
|
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/
|
804
|
-
module
|
805
|
-
def
|
806
|
-
|
1040
|
+
# app/lib/rodauth_ldap.rb
|
1041
|
+
module RodauthLdap
|
1042
|
+
def require_bcrypt?
|
1043
|
+
false
|
807
1044
|
end
|
808
1045
|
|
809
|
-
def
|
810
|
-
|
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
|
1057
|
+
include RodauthLdap
|
821
1058
|
end
|
822
1059
|
# ...
|
823
1060
|
end
|
@@ -826,48 +1063,147 @@ end
|
|
826
1063
|
|
827
1064
|
## Testing
|
828
1065
|
|
829
|
-
|
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/
|
837
|
-
|
838
|
-
|
839
|
-
|
840
|
-
|
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
|
-
|
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
|
-
|
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
|
853
|
-
post "/create-account", params: {
|
854
|
-
|
855
|
-
|
856
|
-
|
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
|
-
|
860
|
-
|
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
|