rodauth-rails 0.9.1 → 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: b8f8aec1dbdc745a530aabec0d63bc2681499dd36f8185faed9ea09e7184636e
4
- data.tar.gz: fbc5a75976a922978a6e37fee3bef8e7f04bb0a9a324066afdf79172b33f00e9
3
+ metadata.gz: 4cc138f57505a1bbf92267e02b8d9b87f50c0dd9b6c114891f649c0b15878637
4
+ data.tar.gz: 28fc8264a8629dd186a446a4d067167d572f8ea58b65c742f12f81a5192221db
5
5
  SHA512:
6
- metadata.gz: 89d2f6ad377ba8e3f18bc747c3bfdf53e97c1a29f2731036987e5f7c1fde14db89732cda2d09026a153d81eabe26e51e021a129f02517d4d5582fcaf392876ca
7
- data.tar.gz: 648b1297a9569b436113b5921a9ae37944d808ed42a03ef57a75452a74143dcc493e7d9c34a12f31f780745db5d2b1365d5a7b602dfa303571961730566852f4
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.9"
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
- 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:
334
355
 
335
356
  ```rb
336
357
  # config/routes.rb
337
358
  Rails.application.routes.draw do
338
- constraints -> (r) { r.env["rodauth"].require_authentication } do
339
- namespace :admin do
340
- # ...
341
- 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
342
397
  end
343
398
  end
344
399
  ```
@@ -406,54 +461,33 @@ end
406
461
 
407
462
  ### Mailer
408
463
 
409
- Depending on the features you've enabled, Rodauth may send emails as part of
410
- 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.
411
467
 
412
468
  ```rb
413
- # app/lib/rodauth_app.rb
414
- class RodauthApp < Rodauth::Rails::App
415
- # ...
416
- configure do
469
+ # app/mailers/rodauth_mailer.rb
470
+ class RodauthMailer < ApplicationMailer
471
+ def verify_account(recipient, email_link)
417
472
  # ...
418
- # general settings
419
- email_from "no-reply@myapp.com"
420
- email_subject_prefix "[MyApp] "
421
- send_email(&:deliver_later)
473
+ end
474
+ def reset_password(recipient, email_link)
422
475
  # ...
423
- # feature settings
424
- verify_account_email_subject "Verify your account"
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
- This approach can be used even if you're using a 3rd-party service for
491
- transactional emails, where emails are sent via HTTP instead of SMTP. Whatever
492
- the `create_*_email` block returns will be passed to `send_email`, so you can
493
- 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.
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
- r.on("admin") { r.rodauth(:admin) }
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
- If you're writing system tests, it's generally better to go through the actual
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/controllers/posts_controller_test.rb
933
- class PostsControllerTest < ActionDispatch::IntegrationTest
934
- test "should require authentication" do
935
- get posts_url
936
- 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
937
1089
 
938
1090
  login
939
- 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
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
- 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"]
944
1155
  end
945
1156
 
946
1157
  private
947
1158
 
948
- def login(login: "user@example.com", password: "secret")
949
- post "/create-account", params: {
950
- "login" => login,
951
- "password" => password,
952
- "password-confirm" => password,
953
- }
954
-
955
- post "/login", params: {
956
- "login" => login,
957
- "password" => password,
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<%= ", :json" if json? %><%= ", :jwt" if jwt? %>
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
- # Uncomment the lines below once you've imported mailer views.
58
- # create_reset_password_email do
59
- # RodauthMailer.reset_password(email_to, reset_password_email_link)
60
- # end
61
- # create_verify_account_email do
62
- # RodauthMailer.verify_account(email_to, verify_account_email_link)
63
- # end
64
- # create_verify_login_change_email do |login|
65
- # RodauthMailer.verify_login_change(login, verify_login_change_old_login, verify_login_change_new_login, verify_login_change_email_link)
66
- # end
67
- # create_password_changed_email do
68
- # RodauthMailer.password_changed(email_to)
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
- # # create_email_auth_email do
71
- # # RodauthMailer.email_auth(email_to, email_auth_email_link)
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
- # In the meantime, you can tweak settings for emails created by Rodauth.
82
- # email_subject_prefix "[MyApp] "
83
- # email_from "noreply@myapp.com"
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 <%= options[:name].camelize %>Mailer < ApplicationMailer
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
@@ -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(name = nil, **options, &block)
21
- plugin :rodauth, name: name, csrf: false, flash: false, json: true, **options do
22
- # load the Rails integration
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
- # database functions are more complex to set up, so disable them by default
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
- # avoid having to set deadline values in column default values
29
- set_deadline_values? true
26
+ auth_class ||= Class.new(Rodauth::Rails::Auth)
30
27
 
31
- # use HMACs for additional security
32
- hmac_secret { Rodauth::Rails.secret_key_base }
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
- (opts[:rodauths] || {}).each do |name, _|
41
- if name
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
- app.after do
12
- if response.empty? && response.headers.any?
13
- env["rodauth.rails.headers"] = response.headers
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: -> (env, res) do
18
- if headers = env.delete("rodauth.rails.headers")
19
- res[1] = headers.merge(res[1])
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
@@ -9,11 +9,7 @@ module Rodauth
9
9
  end
10
10
 
11
11
  def rodauth(name = nil)
12
- if name
13
- request.env["rodauth.#{name}"]
14
- else
15
- request.env["rodauth"]
16
- end
12
+ request.env.fetch ["rodauth", *name].join(".")
17
13
  end
18
14
  end
19
15
  end
@@ -1,5 +1,5 @@
1
1
  module Rodauth
2
2
  module Rails
3
- VERSION = "0.9.1"
3
+ VERSION = "0.10.0"
4
4
  end
5
5
  end
@@ -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.9"
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.9.1
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-02-10 00:00:00.000000000 Z
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.9'
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.9'
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