rodauth-rails 0.9.1 → 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 +14 -0
- data/README.md +316 -76
- data/lib/generators/rodauth/install_generator.rb +17 -0
- data/lib/generators/rodauth/templates/app/lib/rodauth_app.rb +31 -29
- data/lib/generators/rodauth/templates/app/mailers/rodauth_mailer.rb +3 -3
- data/lib/rodauth/rails.rb +10 -0
- data/lib/rodauth/rails/app.rb +11 -21
- 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/version.rb +1 -1
- data/rodauth-rails.gemspec +1 -1
- metadata +5 -5
- 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,17 @@
|
|
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
|
+
|
1
15
|
## 0.9.1 (2021-02-10)
|
2
16
|
|
3
17
|
* Fix flash integration being loaded for API-only apps and causing an error (@dmitryzuev)
|
data/README.md
CHANGED
@@ -83,7 +83,7 @@ documentation][hmac] for instructions on how to safely transition, or just set
|
|
83
83
|
Add the gem to your Gemfile:
|
84
84
|
|
85
85
|
```rb
|
86
|
-
gem "rodauth-rails", "~> 0.
|
86
|
+
gem "rodauth-rails", "~> 0.10"
|
87
87
|
|
88
88
|
# gem "jwt", require: false # for JWT feature
|
89
89
|
# gem "rotp", require: false # for OTP feature
|
@@ -116,6 +116,7 @@ The generator will create the following files:
|
|
116
116
|
* Rodauth app at `app/lib/rodauth_app.rb`
|
117
117
|
* Rodauth controller at `app/controllers/rodauth_controller.rb`
|
118
118
|
* Account model at `app/models/account.rb`
|
119
|
+
* Rodauth mailer at `app/mailers/rodauth_mailer.rb` with views
|
119
120
|
|
120
121
|
### Migration
|
121
122
|
|
@@ -211,6 +212,23 @@ class Account < ApplicationRecord
|
|
211
212
|
end
|
212
213
|
```
|
213
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
|
+
|
214
232
|
## Usage
|
215
233
|
|
216
234
|
### Routes
|
@@ -272,7 +290,7 @@ class ApplicationController < ActionController::Base
|
|
272
290
|
rodauth.logout
|
273
291
|
rodauth.login_required
|
274
292
|
end
|
275
|
-
helper_method :current_account
|
293
|
+
helper_method :current_account # skip if inheriting from ActionController:API
|
276
294
|
end
|
277
295
|
```
|
278
296
|
|
@@ -330,15 +348,52 @@ class PostsController < ApplicationController
|
|
330
348
|
end
|
331
349
|
```
|
332
350
|
|
333
|
-
|
351
|
+
#### Routing constraints
|
352
|
+
|
353
|
+
You can also require authentication at the Rails router level by
|
354
|
+
using a built-in `authenticated` routing constraint:
|
334
355
|
|
335
356
|
```rb
|
336
357
|
# config/routes.rb
|
337
358
|
Rails.application.routes.draw do
|
338
|
-
constraints
|
339
|
-
|
340
|
-
|
341
|
-
|
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
|
342
397
|
end
|
343
398
|
end
|
344
399
|
```
|
@@ -406,54 +461,33 @@ end
|
|
406
461
|
|
407
462
|
### Mailer
|
408
463
|
|
409
|
-
|
410
|
-
|
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.
|
411
467
|
|
412
468
|
```rb
|
413
|
-
# app/
|
414
|
-
class
|
415
|
-
|
416
|
-
configure do
|
469
|
+
# app/mailers/rodauth_mailer.rb
|
470
|
+
class RodauthMailer < ApplicationMailer
|
471
|
+
def verify_account(recipient, email_link)
|
417
472
|
# ...
|
418
|
-
|
419
|
-
|
420
|
-
email_subject_prefix "[MyApp] "
|
421
|
-
send_email(&:deliver_later)
|
473
|
+
end
|
474
|
+
def reset_password(recipient, email_link)
|
422
475
|
# ...
|
423
|
-
|
424
|
-
|
425
|
-
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)
|
426
478
|
# ...
|
427
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
|
428
489
|
end
|
429
490
|
```
|
430
|
-
|
431
|
-
This is convenient when starting out, but eventually you might want to use your
|
432
|
-
own mailer. You can start by running the following command:
|
433
|
-
|
434
|
-
```sh
|
435
|
-
$ rails generate rodauth:mailer
|
436
|
-
```
|
437
|
-
|
438
|
-
This will create a `RodauthMailer` with the associated mailer views in
|
439
|
-
`app/views/rodauth_mailer` directory:
|
440
|
-
|
441
|
-
```rb
|
442
|
-
# app/mailers/rodauth_mailer.rb
|
443
|
-
class RodauthMailer < ApplicationMailer
|
444
|
-
def verify_account(recipient, email_link) ... end
|
445
|
-
def reset_password(recipient, email_link) ... end
|
446
|
-
def verify_login_change(recipient, old_login, new_login, email_link) ... end
|
447
|
-
def password_changed(recipient) ... end
|
448
|
-
# def email_auth(recipient, email_link) ... end
|
449
|
-
# def unlock_account(recipient, email_link) ... end
|
450
|
-
end
|
451
|
-
```
|
452
|
-
|
453
|
-
You can then uncomment the lines in your Rodauth configuration to have it call
|
454
|
-
your mailer. If you've enabled additional authentication features that send
|
455
|
-
emails, make sure to override their `create_*_email` methods as well.
|
456
|
-
|
457
491
|
```rb
|
458
492
|
# app/lib/rodauth_app.rb
|
459
493
|
class RodauthApp < Rodauth::Rails::App
|
@@ -487,10 +521,17 @@ class RodauthApp < Rodauth::Rails::App
|
|
487
521
|
end
|
488
522
|
```
|
489
523
|
|
490
|
-
|
491
|
-
|
492
|
-
|
493
|
-
|
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.
|
494
535
|
|
495
536
|
### Migrations
|
496
537
|
|
@@ -531,12 +572,20 @@ class RodauthApp < Rodauth::Rails::App
|
|
531
572
|
prefix "/admin"
|
532
573
|
session_key_prefix "admin_"
|
533
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
|
534
579
|
# ...
|
535
580
|
end
|
536
581
|
|
537
582
|
route do |r|
|
538
583
|
r.rodauth
|
539
|
-
|
584
|
+
|
585
|
+
r.on "admin" do
|
586
|
+
r.rodauth(:admin)
|
587
|
+
r.pass # allow the Rails app to handle other "/admin/*" requests
|
588
|
+
end
|
540
589
|
# ...
|
541
590
|
end
|
542
591
|
end
|
@@ -548,6 +597,98 @@ Then in your application you can reference the secondary Rodauth instance:
|
|
548
597
|
rodauth(:admin).login_path #=> "/admin/login"
|
549
598
|
```
|
550
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
|
+
|
551
692
|
### Calling controller methods
|
552
693
|
|
553
694
|
When using Rodauth before/after hooks or generally overriding your Rodauth
|
@@ -922,48 +1063,147 @@ end
|
|
922
1063
|
|
923
1064
|
## Testing
|
924
1065
|
|
925
|
-
|
926
|
-
authentication flow with tools like Capybara, and to not use any stubbing.
|
927
|
-
|
928
|
-
In functional and integration tests you can just make requests to Rodauth
|
929
|
-
routes:
|
1066
|
+
System (browser) tests for Rodauth actions could look something like this:
|
930
1067
|
|
931
1068
|
```rb
|
932
|
-
# test/
|
933
|
-
|
934
|
-
|
935
|
-
|
936
|
-
|
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
|
937
1089
|
|
938
1090
|
login
|
939
|
-
|
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
|
940
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
|
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)
|
941
1147
|
|
942
1148
|
logout
|
943
|
-
|
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"]
|
944
1155
|
end
|
945
1156
|
|
946
1157
|
private
|
947
1158
|
|
948
|
-
def
|
949
|
-
post "/create-account", params: {
|
950
|
-
|
951
|
-
|
952
|
-
|
953
|
-
|
954
|
-
|
955
|
-
|
956
|
-
|
957
|
-
|
958
|
-
|
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
|
1170
|
+
|
1171
|
+
def login(email: "user@example.com", password: "secret")
|
1172
|
+
post "/login", as: :json, params: { login: email, password: password }
|
959
1173
|
end
|
960
1174
|
|
961
1175
|
def logout
|
962
|
-
post "/logout"
|
1176
|
+
post "/logout", as: :json, headers: { "Authorization" => headers["Authorization"] }
|
963
1177
|
end
|
964
1178
|
end
|
965
1179
|
```
|
966
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
|
+
|
967
1207
|
## Rodauth defaults
|
968
1208
|
|
969
1209
|
rodauth-rails changes some of the default Rodauth settings for easier setup:
|
@@ -10,6 +10,15 @@ module Rodauth
|
|
10
10
|
include ::ActiveRecord::Generators::Migration
|
11
11
|
include MigrationHelpers
|
12
12
|
|
13
|
+
MAILER_VIEWS = %w[
|
14
|
+
email_auth
|
15
|
+
password_changed
|
16
|
+
reset_password
|
17
|
+
unlock_account
|
18
|
+
verify_account
|
19
|
+
verify_login_change
|
20
|
+
]
|
21
|
+
|
13
22
|
source_root "#{__dir__}/templates"
|
14
23
|
namespace "rodauth:install"
|
15
24
|
|
@@ -47,6 +56,14 @@ module Rodauth
|
|
47
56
|
template "app/models/account.rb"
|
48
57
|
end
|
49
58
|
|
59
|
+
def create_mailer
|
60
|
+
template "app/mailers/rodauth_mailer.rb"
|
61
|
+
|
62
|
+
MAILER_VIEWS.each do |view|
|
63
|
+
template "app/views/rodauth_mailer/#{view}.text.erb"
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
50
67
|
private
|
51
68
|
|
52
69
|
def sequel_uri_scheme
|
@@ -2,10 +2,10 @@ class RodauthApp < Rodauth::Rails::App
|
|
2
2
|
configure do
|
3
3
|
# List of authentication features that are loaded.
|
4
4
|
enable :create_account, :verify_account, :verify_account_grace_period,
|
5
|
-
:login, :logout<%= ", :remember" unless jwt? %>,
|
5
|
+
:login, :logout<%= ", :remember" unless jwt? %><%= ", :json" if json? %><%= ", :jwt" if jwt? %>,
|
6
6
|
:reset_password, :change_password, :change_password_notify,
|
7
7
|
:change_login, :verify_login_change,
|
8
|
-
:close_account
|
8
|
+
:close_account
|
9
9
|
|
10
10
|
# See the Rodauth documentation for the list of available config options:
|
11
11
|
# http://rodauth.jeremyevans.net/documentation.html
|
@@ -23,6 +23,10 @@ class RodauthApp < Rodauth::Rails::App
|
|
23
23
|
|
24
24
|
# Accept only JSON requests.
|
25
25
|
only_json? true
|
26
|
+
|
27
|
+
# Handle login and password confirmation fields on the client side.
|
28
|
+
# require_password_confirmation? false
|
29
|
+
# require_login_confirmation? false
|
26
30
|
<% end -%>
|
27
31
|
|
28
32
|
# Specify the controller used for view rendering and CSRF verification.
|
@@ -54,35 +58,29 @@ class RodauthApp < Rodauth::Rails::App
|
|
54
58
|
# already_logged_in { redirect login_redirect }
|
55
59
|
|
56
60
|
# ==> Emails
|
57
|
-
#
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
61
|
+
# Use a custom mailer for delivering authentication emails.
|
62
|
+
create_reset_password_email do
|
63
|
+
RodauthMailer.reset_password(email_to, reset_password_email_link)
|
64
|
+
end
|
65
|
+
create_verify_account_email do
|
66
|
+
RodauthMailer.verify_account(email_to, verify_account_email_link)
|
67
|
+
end
|
68
|
+
create_verify_login_change_email do |login|
|
69
|
+
RodauthMailer.verify_login_change(login, verify_login_change_old_login, verify_login_change_new_login, verify_login_change_email_link)
|
70
|
+
end
|
71
|
+
create_password_changed_email do
|
72
|
+
RodauthMailer.password_changed(email_to)
|
73
|
+
end
|
74
|
+
# create_email_auth_email do
|
75
|
+
# RodauthMailer.email_auth(email_to, email_auth_email_link)
|
69
76
|
# end
|
70
|
-
#
|
71
|
-
#
|
72
|
-
# # end
|
73
|
-
# # create_unlock_account_email do
|
74
|
-
# # RodauthMailer.unlock_account(email_to, unlock_account_email_link)
|
75
|
-
# # end
|
76
|
-
# send_email do |email|
|
77
|
-
# # queue email delivery on the mailer after the transaction commits
|
78
|
-
# db.after_commit { email.deliver_later }
|
77
|
+
# create_unlock_account_email do
|
78
|
+
# RodauthMailer.unlock_account(email_to, unlock_account_email_link)
|
79
79
|
# end
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
# send_email(&:deliver_later)
|
85
|
-
# reset_password_email_body { "Click here to reset your password: #{reset_password_email_link}" }
|
80
|
+
send_email do |email|
|
81
|
+
# queue email delivery on the mailer after the transaction commits
|
82
|
+
db.after_commit { email.deliver_later }
|
83
|
+
end
|
86
84
|
|
87
85
|
# ==> Flash
|
88
86
|
<% unless json? || jwt? -%>
|
@@ -151,7 +149,9 @@ class RodauthApp < Rodauth::Rails::App
|
|
151
149
|
# verify_account_grace_period 3.days
|
152
150
|
# reset_password_deadline_interval Hash[hours: 6]
|
153
151
|
# verify_login_change_deadline_interval Hash[days: 2]
|
152
|
+
<% unless jwt? -%>
|
154
153
|
# remember_deadline_interval Hash[days: 30]
|
154
|
+
<% end -%>
|
155
155
|
end
|
156
156
|
|
157
157
|
# ==> Multiple configurations
|
@@ -187,6 +187,8 @@ class RodauthApp < Rodauth::Rails::App
|
|
187
187
|
# unless rodauth(:admin).logged_in?
|
188
188
|
# rodauth(:admin).require_http_basic_auth
|
189
189
|
# end
|
190
|
+
#
|
191
|
+
# r.pass # allow the Rails app to handle other "/admin/*" requests
|
190
192
|
# end
|
191
193
|
end
|
192
194
|
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
class
|
1
|
+
class RodauthMailer < ApplicationMailer
|
2
2
|
def verify_account(recipient, email_link)
|
3
3
|
@email_link = email_link
|
4
4
|
|
@@ -25,13 +25,13 @@ class <%= options[:name].camelize %>Mailer < ApplicationMailer
|
|
25
25
|
|
26
26
|
# def email_auth(recipient, email_link)
|
27
27
|
# @email_link = email_link
|
28
|
-
|
28
|
+
#
|
29
29
|
# mail to: recipient
|
30
30
|
# end
|
31
31
|
|
32
32
|
# def unlock_account(recipient, email_link)
|
33
33
|
# @email_link = email_link
|
34
|
-
|
34
|
+
#
|
35
35
|
# mail to: recipient
|
36
36
|
# end
|
37
37
|
end
|
data/lib/rodauth/rails.rb
CHANGED
@@ -8,6 +8,7 @@ module Rodauth
|
|
8
8
|
|
9
9
|
# This allows the developer to avoid loading Rodauth at boot time.
|
10
10
|
autoload :App, "rodauth/rails/app"
|
11
|
+
autoload :Auth, "rodauth/rails/auth"
|
11
12
|
|
12
13
|
@app = nil
|
13
14
|
@middleware = true
|
@@ -32,6 +33,15 @@ module Rodauth
|
|
32
33
|
scope.rodauth(name)
|
33
34
|
end
|
34
35
|
|
36
|
+
# routing constraint that requires authentication
|
37
|
+
def authenticated(name = nil, &condition)
|
38
|
+
lambda do |request|
|
39
|
+
rodauth = request.env.fetch ["rodauth", *name].join(".")
|
40
|
+
rodauth.require_authentication
|
41
|
+
rodauth.authenticated? && (condition.nil? || condition.call(rodauth))
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
35
45
|
if ::Rails.gem_version >= Gem::Version.new("5.2")
|
36
46
|
def secret_key_base
|
37
47
|
::Rails.application.secret_key_base
|
data/lib/rodauth/rails/app.rb
CHANGED
@@ -1,6 +1,5 @@
|
|
1
1
|
require "roda"
|
2
|
-
require "rodauth"
|
3
|
-
require "rodauth/rails/feature"
|
2
|
+
require "rodauth/rails/auth"
|
4
3
|
|
5
4
|
module Rodauth
|
6
5
|
module Rails
|
@@ -11,38 +10,29 @@ module Rodauth
|
|
11
10
|
|
12
11
|
plugin :hooks
|
13
12
|
plugin :render, layout: false
|
13
|
+
plugin :pass
|
14
14
|
|
15
15
|
unless Rodauth::Rails.api_only?
|
16
16
|
require "rodauth/rails/app/flash"
|
17
17
|
plugin Flash
|
18
18
|
end
|
19
19
|
|
20
|
-
def self.configure(
|
21
|
-
|
22
|
-
|
23
|
-
enable :rails
|
20
|
+
def self.configure(*args, **options, &block)
|
21
|
+
auth_class = args.shift if args[0].is_a?(Class)
|
22
|
+
name = args.shift if args[0].is_a?(Symbol)
|
24
23
|
|
25
|
-
|
26
|
-
use_database_authentication_functions? false
|
24
|
+
fail ArgumentError, "need to pass optional Rodauth::Auth subclass and optional configuration name" if args.any?
|
27
25
|
|
28
|
-
|
29
|
-
set_deadline_values? true
|
26
|
+
auth_class ||= Class.new(Rodauth::Rails::Auth)
|
30
27
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
# evaluate user configuration
|
35
|
-
instance_exec(&block)
|
28
|
+
plugin :rodauth, auth_class: auth_class, name: name, csrf: false, flash: false, json: true, **options do
|
29
|
+
instance_exec(&block) if block
|
36
30
|
end
|
37
31
|
end
|
38
32
|
|
39
33
|
before do
|
40
|
-
|
41
|
-
|
42
|
-
env["rodauth.#{name}"] = rodauth(name)
|
43
|
-
else
|
44
|
-
env["rodauth"] = rodauth
|
45
|
-
end
|
34
|
+
opts[:rodauths]&.each_key do |name|
|
35
|
+
env[["rodauth", *name].join(".")] = rodauth(name)
|
46
36
|
end
|
47
37
|
end
|
48
38
|
end
|
@@ -3,20 +3,30 @@ module Rodauth
|
|
3
3
|
class App
|
4
4
|
# Roda plugin that extends middleware plugin by propagating response headers.
|
5
5
|
module Middleware
|
6
|
-
def self.load_dependencies(app)
|
7
|
-
app.plugin :hooks
|
8
|
-
end
|
9
|
-
|
10
6
|
def self.configure(app)
|
11
|
-
|
12
|
-
if
|
13
|
-
|
7
|
+
handle_result = -> (env, res) do
|
8
|
+
if headers = env.delete("rodauth.rails.headers")
|
9
|
+
res[1] = headers.merge(res[1])
|
14
10
|
end
|
15
11
|
end
|
16
12
|
|
17
|
-
app.plugin :middleware, handle_result:
|
18
|
-
|
19
|
-
|
13
|
+
app.plugin :middleware, handle_result: handle_result do |middleware|
|
14
|
+
middleware.plugin :hooks
|
15
|
+
|
16
|
+
middleware.after do
|
17
|
+
if response.empty? && response.headers.any?
|
18
|
+
env["rodauth.rails.headers"] = response.headers
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
middleware.class_eval do
|
23
|
+
def self.inspect
|
24
|
+
"#{superclass}::Middleware"
|
25
|
+
end
|
26
|
+
|
27
|
+
def inspect
|
28
|
+
"#<#{self.class.inspect} request=#{request.inspect} response=#{response.inspect}>"
|
29
|
+
end
|
20
30
|
end
|
21
31
|
end
|
22
32
|
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require "rodauth"
|
2
|
+
require "rodauth/rails/feature"
|
3
|
+
|
4
|
+
module Rodauth
|
5
|
+
module Rails
|
6
|
+
# Base auth class that applies some default configuration and supports
|
7
|
+
# multi-level inheritance.
|
8
|
+
class Auth < Rodauth::Auth
|
9
|
+
class << self
|
10
|
+
attr_writer :features
|
11
|
+
attr_writer :routes
|
12
|
+
attr_accessor :configuration
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.inherited(auth_class)
|
16
|
+
super
|
17
|
+
auth_class.roda_class = Rodauth::Rails.app
|
18
|
+
auth_class.features = features.dup
|
19
|
+
auth_class.routes = routes.dup
|
20
|
+
auth_class.route_hash = route_hash.dup
|
21
|
+
auth_class.configuration = configuration.clone
|
22
|
+
auth_class.configuration.instance_variable_set(:@auth, auth_class)
|
23
|
+
end
|
24
|
+
|
25
|
+
# apply default configuration
|
26
|
+
configure do
|
27
|
+
enable :rails
|
28
|
+
|
29
|
+
# database functions are more complex to set up, so disable them by default
|
30
|
+
use_database_authentication_functions? false
|
31
|
+
|
32
|
+
# avoid having to set deadline values in column default values
|
33
|
+
set_deadline_values? true
|
34
|
+
|
35
|
+
# use HMACs for additional security
|
36
|
+
hmac_secret { Rodauth::Rails.secret_key_base }
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
data/rodauth-rails.gemspec
CHANGED
@@ -17,7 +17,7 @@ Gem::Specification.new do |spec|
|
|
17
17
|
spec.require_paths = ["lib"]
|
18
18
|
|
19
19
|
spec.add_dependency "railties", ">= 4.2", "< 7"
|
20
|
-
spec.add_dependency "rodauth", "~> 2.
|
20
|
+
spec.add_dependency "rodauth", "~> 2.11"
|
21
21
|
spec.add_dependency "sequel-activerecord_connection", "~> 1.1"
|
22
22
|
spec.add_dependency "tilt"
|
23
23
|
spec.add_dependency "bcrypt"
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rodauth-rails
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.10.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Janko Marohnić
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-
|
11
|
+
date: 2021-03-23 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: railties
|
@@ -36,14 +36,14 @@ dependencies:
|
|
36
36
|
requirements:
|
37
37
|
- - "~>"
|
38
38
|
- !ruby/object:Gem::Version
|
39
|
-
version: '2.
|
39
|
+
version: '2.11'
|
40
40
|
type: :runtime
|
41
41
|
prerelease: false
|
42
42
|
version_requirements: !ruby/object:Gem::Requirement
|
43
43
|
requirements:
|
44
44
|
- - "~>"
|
45
45
|
- !ruby/object:Gem::Version
|
46
|
-
version: '2.
|
46
|
+
version: '2.11'
|
47
47
|
- !ruby/object:Gem::Dependency
|
48
48
|
name: sequel-activerecord_connection
|
49
49
|
requirement: !ruby/object:Gem::Requirement
|
@@ -111,7 +111,6 @@ files:
|
|
111
111
|
- LICENSE.txt
|
112
112
|
- README.md
|
113
113
|
- lib/generators/rodauth/install_generator.rb
|
114
|
-
- lib/generators/rodauth/mailer_generator.rb
|
115
114
|
- lib/generators/rodauth/migration/account_expiration.erb
|
116
115
|
- lib/generators/rodauth/migration/active_sessions.erb
|
117
116
|
- lib/generators/rodauth/migration/audit_logging.erb
|
@@ -205,6 +204,7 @@ files:
|
|
205
204
|
- lib/rodauth/rails/app.rb
|
206
205
|
- lib/rodauth/rails/app/flash.rb
|
207
206
|
- lib/rodauth/rails/app/middleware.rb
|
207
|
+
- lib/rodauth/rails/auth.rb
|
208
208
|
- lib/rodauth/rails/controller_methods.rb
|
209
209
|
- lib/rodauth/rails/feature.rb
|
210
210
|
- lib/rodauth/rails/middleware.rb
|
@@ -1,37 +0,0 @@
|
|
1
|
-
require "rails/generators/base"
|
2
|
-
|
3
|
-
module Rodauth
|
4
|
-
module Rails
|
5
|
-
module Generators
|
6
|
-
class MailerGenerator < ::Rails::Generators::Base
|
7
|
-
source_root "#{__dir__}/templates"
|
8
|
-
namespace "rodauth:mailer"
|
9
|
-
|
10
|
-
VIEWS = %w[
|
11
|
-
email_auth
|
12
|
-
password_changed
|
13
|
-
reset_password
|
14
|
-
unlock_account
|
15
|
-
verify_account
|
16
|
-
verify_login_change
|
17
|
-
]
|
18
|
-
|
19
|
-
class_option :name,
|
20
|
-
desc: "The name for the mailer and the views directory",
|
21
|
-
default: "rodauth"
|
22
|
-
|
23
|
-
def copy_mailer
|
24
|
-
template "app/mailers/rodauth_mailer.rb",
|
25
|
-
"app/mailers/#{options[:name].underscore}_mailer.rb"
|
26
|
-
end
|
27
|
-
|
28
|
-
def copy_mailer_views
|
29
|
-
VIEWS.each do |view|
|
30
|
-
template "app/views/rodauth_mailer/#{view}.text.erb",
|
31
|
-
"app/views/#{options[:name].underscore}_mailer/#{view}.text.erb"
|
32
|
-
end
|
33
|
-
end
|
34
|
-
end
|
35
|
-
end
|
36
|
-
end
|
37
|
-
end
|