rodauth-rails 0.7.0 → 0.8.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: 8163d64892cbebd867182d15148f3099abb3ed49ae3e07a89a5adea6606623d2
4
- data.tar.gz: 3cc7990e0af8e5ffb2ac959f989fb45cf538490412adfc908571823e5dd7b160
3
+ metadata.gz: 0f1312dbd1bb4dc0d954c77a5ff350b5c9e1ff3fc4dd45b8834cd3e7d0280a22
4
+ data.tar.gz: 5dda5720126361589a428add9b8256b35aa53644166ca7a8a6d14c5baef53f02
5
5
  SHA512:
6
- metadata.gz: 99005d6864310fa3a36f8314a13588900a5ac1559af7a77d75cb5aba66b0b829d32c83fe66a3f5a7ced098de32b39396edd666919177836bb84b35a0de3a558b
7
- data.tar.gz: 2d66b5ab43d05b26483cb3d69181c506b19a937fa77a2d7d66a38708f6357fae7bd2e605cc0a96affdd8fed822076dccb1603577338e68620c70a816fc45db7a
6
+ metadata.gz: f70e5a44db25c016fe92169be342d1f489cd0e3307fe6c06dbe822c28c05f55dc696b26721836d315daabbbfb0889d18357cec3bb7aa52932649f5ecb08ceedb
7
+ data.tar.gz: 6af3cd43f9266729049d984c9da58beff019dd2f0148465d65c8f814602d9a9678308d752ef469f08ab381a1373b919d1e61b62b204751d95ca57d10ed05de2a
@@ -1,3 +1,9 @@
1
+ ## 0.8.0 (2021-01-03)
2
+
3
+ * Add `--api` option to `rodauth:install` generator for choosing JSON-only configuration (@janko)
4
+
5
+ * Don't blow up when a Rodauth request is made using an unsupported HTTP verb (@janko)
6
+
1
7
  ## 0.7.0 (2020-11-27)
2
8
 
3
9
  * Add `#rails_controller_eval` method for running code in context of a controller instance (@janko)
data/README.md CHANGED
@@ -12,7 +12,25 @@ Useful links:
12
12
  Articles:
13
13
 
14
14
  * [Rodauth: A Refreshing Authentication Solution for Ruby](https://janko.io/rodauth-a-refreshing-authentication-solution-for-ruby/)
15
- * [Adding Authentication in Rails 6 with Rodauth](https://janko.io/adding-authentication-in-rails-with-rodauth/)
15
+ * [Adding Authentication in Rails with Rodauth](https://janko.io/adding-authentication-in-rails-with-rodauth/)
16
+ * [Adding Multifactor Authentication in Rails with Rodauth](https://janko.io/adding-multifactor-authentication-in-rails-with-rodauth/)
17
+
18
+ ## Why Rodauth?
19
+
20
+ There are already several popular authentication solutions for Rails (Devise,
21
+ Sorcery, Clearance, Authlogic), so why would you choose Rodauth? Well, because
22
+ it has many advantages over the mentioned alternatives:
23
+
24
+ * multifactor authentication ([TOTP][otp], [SMS codes][sms_codes], [recovery codes][recovery_codes], [WebAuthn][webauthn])
25
+ * standardized [JSON API support][jwt] (for every feature)
26
+ * 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
+ * [email authentication][email_auth] (aka "passwordless")
28
+ * [audit logging][audit_logging] (for any action)
29
+ * ability to protect password hashes even in case of SQL injection ([more details][password protection])
30
+ * additional bruteforce protection for tokens ([more details][bruteforce tokens])
31
+ * uniform configuration DSL (any setting can be static or dynamic)
32
+ * consistent before/after hooks around everything
33
+ * dedicated object encapsulating all authentication logic
16
34
 
17
35
  ## Upgrading
18
36
 
@@ -21,14 +39,14 @@ Articles:
21
39
  Starting from version 0.7.0, rodauth-rails now correctly detects Rails
22
40
  application's `secret_key_base` when setting default `hmac_secret`, including
23
41
  when it's set via credentials or `$SECRET_KEY_BASE` environment variable. This
24
- means authentication will be more secure by default, and Rodauth features that
25
- require `hmac_secret` should now work automatically as well.
42
+ means that your authentication will now be more secure by default, and Rodauth
43
+ features that require `hmac_secret` should now work automatically as well.
26
44
 
27
45
  However, if you've already been using rodauth-rails in production, where the
28
46
  `secret_key_base` is set via credentials or environment variable and `hmac_secret`
29
47
  was not explicitly set, the fact that your authentication will now start using
30
48
  HMACs has backwards compatibility considerations. See the [Rodauth
31
- documentation](hmac) for instructions on how to safely transition, or just set
49
+ documentation][hmac] for instructions on how to safely transition, or just set
32
50
  `hmac_secret nil` in your Rodauth configuration.
33
51
 
34
52
  ## Installation
@@ -48,10 +66,17 @@ Then run `bundle install`.
48
66
 
49
67
  Next, run the install generator:
50
68
 
51
- ```
69
+ ```sh
52
70
  $ rails generate rodauth:install
53
71
  ```
54
72
 
73
+ Or if you want Rodauth endpoints to be exposed via JSON API:
74
+
75
+ ```sh
76
+ $ rails generate rodauth:install --api
77
+ $ bundle add jwt
78
+ ```
79
+
55
80
  The generator will create the following files:
56
81
 
57
82
  * Rodauth migration at `db/migrate/*_create_rodauth.rb`
@@ -185,14 +210,12 @@ Using this information, we could add some basic authentication links to our
185
210
  navigation header:
186
211
 
187
212
  ```erb
188
- <ul>
189
- <% if rodauth.authenticated? %>
190
- <li><%= link_to "Sign out", rodauth.logout_path, method: :post %></li>
191
- <% else %>
192
- <li><%= link_to "Sign in", rodauth.login_path %></li>
193
- <li><%= link_to "Sign up", rodauth.create_account_path %></li>
194
- <% end %>
195
- </ul>
213
+ <% if rodauth.logged_in? %>
214
+ <%= link_to "Sign out", rodauth.logout_path, method: :post %>
215
+ <% else %>
216
+ <%= link_to "Sign in", rodauth.login_path %>
217
+ <%= link_to "Sign up", rodauth.create_account_path %>
218
+ <% end %>
196
219
  ```
197
220
 
198
221
  These routes are fully functional, feel free to visit them and interact with the
@@ -208,7 +231,7 @@ retrieves the corresponding account record:
208
231
  ```rb
209
232
  # app/controllers/application_controller.rb
210
233
  class ApplicationController < ActionController::Base
211
- before_action :current_account, if: -> { rodauth.authenticated? }
234
+ before_action :current_account, if: -> { rodauth.logged_in? }
212
235
 
213
236
  private
214
237
 
@@ -382,7 +405,7 @@ $ rails generate rodauth:mailer
382
405
  ```
383
406
 
384
407
  This will create a `RodauthMailer` with the associated mailer views in
385
- `app/views/rodauth_mailer` directory.
408
+ `app/views/rodauth_mailer` directory:
386
409
 
387
410
  ```rb
388
411
  # app/mailers/rodauth_mailer.rb
@@ -434,9 +457,9 @@ end
434
457
  ```
435
458
 
436
459
  This approach can be used even if you're using a 3rd-party service for
437
- transactional emails, where emails are sent via API requests instead of
438
- SMTP. Whatever the `create_*_email` block returns will be passed to
439
- `send_email`, so you can be creative.
460
+ transactional emails, where emails are sent via HTTP instead of SMTP. Whatever
461
+ the `create_*_email` block returns will be passed to `send_email`, so you can
462
+ be creative.
440
463
 
441
464
  ### Migrations
442
465
 
@@ -458,37 +481,6 @@ class CreateRodauthOtpSmsCodesRecoveryCodes < ActiveRecord::Migration
458
481
  end
459
482
  ```
460
483
 
461
- ### JSON API
462
-
463
- JSON API support in Rodauth is provided by the [JWT feature]. First you'll need
464
- to add the [JWT gem] to your Gemfile:
465
-
466
- ```rb
467
- gem "jwt"
468
- ```
469
-
470
- The following configuration will enable the Rodauth endpoints to be accessed
471
- via JSON requests (in addition to HTML requests):
472
-
473
- ```rb
474
- # app/lib/rodauth_app.rb
475
- class RodauthApp < Rodauth::Rails::App
476
- configure(json: true) do
477
- # ...
478
- enable :jwt
479
- jwt_secret "...your secret key..."
480
- # ...
481
- end
482
- end
483
- ```
484
-
485
- If you want the endpoints to be only accessible via JSON requests, or if your
486
- Rails app is in API-only mode, instead of `json: true` pass `json: :only` to
487
- the configure method.
488
-
489
- Make sure to store the `jwt_secret` in a secure place, such as Rails
490
- credentials or environment variables.
491
-
492
484
  ### Calling controller methods
493
485
 
494
486
  When using Rodauth before/after hooks or generally overriding your Rodauth
@@ -585,13 +577,38 @@ integration for Rodauth:
585
577
  * runs Action Controller callbacks & rescue handlers around Rodauth actions
586
578
  * uses Action Mailer for sending emails
587
579
 
588
- The `configure { ... }` method wraps configuring the Rodauth plugin, forwarding
580
+ The `configure` method wraps configuring the Rodauth plugin, forwarding
589
581
  any additional [plugin options].
590
582
 
591
583
  ```rb
592
- configure { ... } # defining default Rodauth configuration
593
- configure(json: true) { ... } # passing options to the Rodauth plugin
594
- configure(:secondary) { ... } # defining multiple Rodauth configurations
584
+ class RodauthApp < Rodauth::Rails::App
585
+ configure { ... } # defining default Rodauth configuration
586
+ configure(json: true) { ... } # passing options to the Rodauth plugin
587
+ configure(:secondary) { ... } # defining multiple Rodauth configurations
588
+ end
589
+ ```
590
+
591
+ The `route` block is provided by Roda, and it's called on each request before
592
+ it reaches the Rails router.
593
+
594
+ ```rb
595
+ class RodauthApp < Rodauth::Rails::App
596
+ route do |r|
597
+ # ... called before each request ...
598
+ end
599
+ end
600
+ ```
601
+
602
+ Since `Rodauth::Rails::App` is just a Roda subclass, you can do anything you
603
+ would with a Roda app, such as loading additional Roda plugins:
604
+
605
+ ```rb
606
+ class RodauthApp < Rodauth::Rails::App
607
+ plugin :request_headers # easier access to request headers
608
+ plugin :typecast_params # methods for conversion of request params
609
+ plugin :default_headers, { "Foo" => "Bar" }
610
+ # ...
611
+ end
595
612
  ```
596
613
 
597
614
  ### Sequel
@@ -607,6 +624,142 @@ connection (using the [sequel-activerecord_connection] gem).
607
624
  This means that, from the usage perspective, Sequel can be considered just
608
625
  as an implementation detail of Rodauth.
609
626
 
627
+ ## JSON API
628
+
629
+ JSON API support in Rodauth is provided by the [JWT feature][jwt]. You'll need
630
+ to install the [JWT gem], enable JSON support and enable the JWT feature:
631
+
632
+ ```sh
633
+ $ bundle add jwt
634
+ ```
635
+ ```rb
636
+ # app/lib/rodauth_app.rb
637
+ class RodauthApp < Rodauth::Rails::App
638
+ configure(json: :only) do
639
+ # ...
640
+ enable :jwt
641
+ # make sure to store the JWT secret below in a safe place
642
+ jwt_secret "...your secret key..."
643
+ # ...
644
+ end
645
+ end
646
+ ```
647
+
648
+ With the above configuration, Rodauth routes will only be accessible via JSON
649
+ requests. If you still want to allow HTML access alongside JSON, change `json:
650
+ :only` to `json: true`.
651
+
652
+ Emails will automatically work in JSON-only mode, because `Rodauth::Rails::App`
653
+ comes with Roda's `render` plugin loaded. They are customized the same as in
654
+ the non-JSON case.
655
+
656
+ ## OmniAuth
657
+
658
+ While Rodauth doesn't yet come with [OmniAuth] integration, we can build one
659
+ ourselves using the existing Rodauth API.
660
+
661
+ In order to allow the user to login via multiple external providers, let's
662
+ create an `account_identities` table that will have a many-to-one relationship
663
+ with the `accounts` table:
664
+
665
+ ```sh
666
+ $ rails generate model AccountIdentity
667
+ ```
668
+ ```rb
669
+ # db/migrate/*_create_account_identities.rb
670
+ class CreateAccountIdentities < ActiveRecord::Migration
671
+ def change
672
+ create_table :account_identities do |t|
673
+ t.references :account, null: false, foreign_key: { on_delete: :cascade }
674
+ t.string :provider, null: false
675
+ t.string :uid, null: false
676
+ t.jsonb :info, null: false, default: {} # adjust JSON column type for your database
677
+
678
+ t.timestamps
679
+
680
+ t.index [:provider, :uid], unique: true
681
+ end
682
+ end
683
+ end
684
+ ```
685
+ ```rb
686
+ # app/models/account_identity.rb
687
+ class AcccountIdentity < ApplicationRecord
688
+ belongs_to :account
689
+ end
690
+ ```
691
+ ```rb
692
+ # app/models/account.rb
693
+ class Account < ApplicationRecord
694
+ has_many :identities, class_name: "AccountIdentity"
695
+ end
696
+ ```
697
+
698
+ Let's assume we want to implement Facebook login, and have added the
699
+ corresponding OmniAuth strategy to the middleware stack, together with an
700
+ authorization link on the login form:
701
+
702
+ ```rb
703
+ Rails.application.config.middleware.use OmniAuth::Builder do
704
+ provider :facebook, ENV["FACEBOOK_APP_ID"], ENV["FACEBOOK_APP_SECRET"],
705
+ scope: "email", callback_path: "/auth/facebook/callback"
706
+ end
707
+ ```
708
+ ```erb
709
+ <%= link_to "Login via Facebook", "/auth/facebook" %>
710
+ ```
711
+
712
+ Let's implement the OmniAuth callback endpoint on our Rodauth controller:
713
+
714
+ ```rb
715
+ # config/routes.rb
716
+ Rails.application.routes.draw do
717
+ # ...
718
+ get "/auth/:provider/callback", to: "rodauth#omniauth"
719
+ end
720
+ ```
721
+ ```rb
722
+ # app/controllres/rodauth_controller.rb
723
+ class RodauthController < ApplicationController
724
+ def omniauth
725
+ auth = request.env["omniauth.auth"]
726
+
727
+ # attempt to find existing identity directly
728
+ identity = AccountIdentity.find_by(provider: auth["provider"], uid: auth["uid"])
729
+
730
+ if identity
731
+ # update any external info changes
732
+ identity.update!(info: auth["info"])
733
+ # set account from identity
734
+ account = identity.account
735
+ end
736
+
737
+ # attempt to find an existing account by email
738
+ account ||= Account.find_by(email: auth["info"]["email"])
739
+
740
+ # disallow login if account is not verified
741
+ if account && account.status != rodauth.account_open_status_value
742
+ redirect_to rodauth.login_path, alert: rodauth.unverified_account_message
743
+ return
744
+ end
745
+
746
+ # create new account if it doesn't exist
747
+ unless account
748
+ account = Account.create!(email: auth["info"]["email"])
749
+ end
750
+
751
+ # create new identity if it doesn't exist
752
+ unless identity
753
+ account.identities.create!(provider: auth["provider"], uid: auth["uid"], info: auth["info"])
754
+ end
755
+
756
+ # login with Rodauth
757
+ rodauth.account_from_login(account.email)
758
+ rodauth.login("omniauth")
759
+ end
760
+ end
761
+ ```
762
+
610
763
  ## Configuring
611
764
 
612
765
  For the list of configuration methods provided by Rodauth, see the [feature
@@ -640,6 +793,37 @@ Rodauth::Rails.configure do |config|
640
793
  end
641
794
  ```
642
795
 
796
+ ## Custom extensions
797
+
798
+ When developing custom extensions for Rodauth inside your Rails project, it's
799
+ better to use plain modules (at least in the beginning), because Rodauth
800
+ feature API doesn't yet support Zeitwerk reloading well.
801
+
802
+ ```rb
803
+ # app/lib/rodauth_argon2.rb
804
+ module RodauthArgon2
805
+ def password_hash(password)
806
+ Argon2::Password.create(password, t_cost: password_hash_cost, m_cost: password_hash_cost)
807
+ end
808
+
809
+ def password_hash_match?(hash, password)
810
+ Argon2::Password.verify_password(password, hash)
811
+ end
812
+ end
813
+ ```
814
+ ```rb
815
+ # app/lib/rodauth_app.rb
816
+ class RodauthApp < Rodauth::Rails::App
817
+ configure do
818
+ # ...
819
+ auth_class_eval do
820
+ include RodauthArgon2
821
+ end
822
+ # ...
823
+ end
824
+ end
825
+ ```
826
+
643
827
  ## Testing
644
828
 
645
829
  If you're writing system tests, it's generally better to go through the actual
@@ -712,6 +896,8 @@ Rodauth method for creating database functions:
712
896
 
713
897
  ```rb
714
898
  # db/migrate/*_create_rodauth_database_functions.rb
899
+ require "rodauth/migrations"
900
+
715
901
  class CreateRodauthDatabaseFunctions < ActiveRecord::Migration
716
902
  def up
717
903
  Rodauth.create_database_authentication_functions(DB)
@@ -776,7 +962,6 @@ conduct](https://github.com/janko/rodauth-rails/blob/master/CODE_OF_CONDUCT.md).
776
962
  [Rodauth]: https://github.com/jeremyevans/rodauth
777
963
  [Sequel]: https://github.com/jeremyevans/sequel
778
964
  [feature documentation]: http://rodauth.jeremyevans.net/documentation.html
779
- [JWT feature]: http://rodauth.jeremyevans.net/rdoc/files/doc/jwt_rdoc.html
780
965
  [JWT gem]: https://github.com/jwt/ruby-jwt
781
966
  [Bootstrap]: https://getbootstrap.com/
782
967
  [Roda]: http://roda.jeremyevans.net/
@@ -786,3 +971,19 @@ conduct](https://github.com/janko/rodauth-rails/blob/master/CODE_OF_CONDUCT.md).
786
971
  [sequel-activerecord_connection]: https://github.com/janko/sequel-activerecord_connection
787
972
  [plugin options]: http://rodauth.jeremyevans.net/rdoc/files/README_rdoc.html#label-Plugin+Options
788
973
  [hmac]: http://rodauth.jeremyevans.net/rdoc/files/README_rdoc.html#label-HMAC
974
+ [OmniAuth]: https://github.com/omniauth/omniauth
975
+ [otp]: http://rodauth.jeremyevans.net/rdoc/files/doc/otp_rdoc.html
976
+ [sms_codes]: http://rodauth.jeremyevans.net/rdoc/files/doc/sms_codes_rdoc.html
977
+ [recovery_codes]: http://rodauth.jeremyevans.net/rdoc/files/doc/recovery_codes_rdoc.html
978
+ [webauthn]: http://rodauth.jeremyevans.net/rdoc/files/doc/webauthn_rdoc.html
979
+ [jwt]: http://rodauth.jeremyevans.net/rdoc/files/doc/jwt_rdoc.html
980
+ [email_auth]: http://rodauth.jeremyevans.net/rdoc/files/doc/email_auth_rdoc.html
981
+ [audit_logging]: http://rodauth.jeremyevans.net/rdoc/files/doc/audit_logging_rdoc.html
982
+ [password protection]: https://github.com/jeremyevans/rodauth#label-Password+Hash+Access+Via+Database+Functions
983
+ [bruteforce tokens]: https://github.com/jeremyevans/rodauth#label-Tokens
984
+ [password_complexity]: http://rodauth.jeremyevans.net/rdoc/files/doc/password_complexity_rdoc.html
985
+ [disallow_password_reuse]: http://rodauth.jeremyevans.net/rdoc/files/doc/disallow_password_reuse_rdoc.html
986
+ [password_expiration]: http://rodauth.jeremyevans.net/rdoc/files/doc/password_expiration_rdoc.html
987
+ [session_expiration]: http://rodauth.jeremyevans.net/rdoc/files/doc/session_expiration_rdoc.html
988
+ [single_session]: http://rodauth.jeremyevans.net/rdoc/files/doc/single_session_rdoc.html
989
+ [account_expiration]: http://rodauth.jeremyevans.net/rdoc/files/doc/account_expiration_rdoc.html
@@ -13,6 +13,15 @@ module Rodauth
13
13
  source_root "#{__dir__}/templates"
14
14
  namespace "rodauth:install"
15
15
 
16
+ # The :api option is a Rails-recognized option that always
17
+ # defaults to false, so we make it use our provided default
18
+ # value instead.
19
+ def self.default_value_for_option(name, options)
20
+ name == :api ? options[:default] : super
21
+ end
22
+
23
+ class_option :api, type: :boolean, desc: "Generate JSON-only configuration"
24
+
16
25
  def create_rodauth_migration
17
26
  return unless defined?(ActiveRecord::Base)
18
27
 
@@ -75,9 +84,11 @@ module Rodauth
75
84
  end
76
85
 
77
86
  def api_only?
78
- return unless ::Rails.gem_version >= Gem::Version.new("5.0")
79
-
80
- ::Rails.application.config.api_only
87
+ if options.key?(:api)
88
+ options[:api]
89
+ elsif ::Rails.gem_version >= Gem::Version.new("5.0")
90
+ ::Rails.application.config.api_only
91
+ end
81
92
  end
82
93
 
83
94
  def migration_features
@@ -1,4 +1,6 @@
1
1
  require "roda"
2
+ require "rodauth"
3
+ require "rodauth/rails/feature"
2
4
 
3
5
  module Rodauth
4
6
  module Rails
@@ -68,9 +68,11 @@ module Rodauth
68
68
 
69
69
  if rails_controller_instance.performed?
70
70
  rails_controller_response
71
- else
71
+ elsif result
72
72
  result[1].merge!(rails_controller_instance.response.headers)
73
73
  throw :halt, result
74
+ else
75
+ result
74
76
  end
75
77
  end
76
78
 
@@ -1,5 +1,5 @@
1
1
  module Rodauth
2
2
  module Rails
3
- VERSION = "0.7.0"
3
+ VERSION = "0.8.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.6"
20
+ spec.add_dependency "rodauth", "~> 2.7"
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.7.0
4
+ version: 0.8.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: 2020-11-27 00:00:00.000000000 Z
11
+ date: 2021-01-03 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.6'
39
+ version: '2.7'
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.6'
46
+ version: '2.7'
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: sequel-activerecord_connection
49
49
  requirement: !ruby/object:Gem::Requirement
@@ -187,7 +187,6 @@ files:
187
187
  - lib/generators/rodauth/templates/db/migrate/create_rodauth.rb
188
188
  - lib/generators/rodauth/views_generator.rb
189
189
  - lib/rodauth-rails.rb
190
- - lib/rodauth/features/rails.rb
191
190
  - lib/rodauth/rails.rb
192
191
  - lib/rodauth/rails/app.rb
193
192
  - lib/rodauth/rails/app/flash.rb
@@ -1 +0,0 @@
1
- require "rodauth/rails/feature"