rodauth-rails 0.11.0 → 0.12.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 +12 -0
- data/README.md +36 -144
- data/lib/generators/rodauth/templates/app/views/rodauth/_global_logout_field.html.erb +1 -1
- data/lib/generators/rodauth/templates/app/views/rodauth/_login_confirm_field.html.erb +2 -2
- data/lib/generators/rodauth/templates/app/views/rodauth/_login_display.html.erb +2 -2
- data/lib/generators/rodauth/templates/app/views/rodauth/_login_field.html.erb +2 -2
- data/lib/generators/rodauth/templates/app/views/rodauth/_new_password_field.html.erb +2 -2
- data/lib/generators/rodauth/templates/app/views/rodauth/_otp_auth_code_field.html.erb +2 -2
- data/lib/generators/rodauth/templates/app/views/rodauth/_password_confirm_field.html.erb +2 -2
- data/lib/generators/rodauth/templates/app/views/rodauth/_password_field.html.erb +2 -2
- data/lib/generators/rodauth/templates/app/views/rodauth/_recovery_code_field.html.erb +2 -2
- data/lib/generators/rodauth/templates/app/views/rodauth/_sms_code_field.html.erb +2 -2
- data/lib/generators/rodauth/templates/app/views/rodauth/_sms_phone_field.html.erb +2 -2
- data/lib/generators/rodauth/templates/app/views/rodauth/_submit.html.erb +1 -1
- data/lib/generators/rodauth/templates/app/views/rodauth/otp_setup.html.erb +2 -2
- data/lib/generators/rodauth/templates/app/views/rodauth/remember.html.erb +1 -1
- data/lib/generators/rodauth/templates/app/views/rodauth/webauthn_remove.html.erb +1 -1
- data/lib/rodauth/rails/feature.rb +17 -230
- data/lib/rodauth/rails/feature/base.rb +62 -0
- data/lib/rodauth/rails/feature/callbacks.rb +61 -0
- data/lib/rodauth/rails/feature/csrf.rb +65 -0
- data/lib/rodauth/rails/feature/email.rb +30 -0
- data/lib/rodauth/rails/feature/instrumentation.rb +71 -0
- data/lib/rodauth/rails/feature/render.rb +41 -0
- data/lib/rodauth/rails/railtie.rb +0 -5
- data/lib/rodauth/rails/version.rb +1 -1
- metadata +8 -3
- data/lib/rodauth/rails/log_subscriber.rb +0 -34
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: 27d48e6bf86cf81b33f6b0282048c2fb6f16ec6602136e18de6ede5120cfd808
         | 
| 4 | 
            +
              data.tar.gz: 2f79498ff25a42131a5ead77f3d4adf05152bc85f271c8b985f0f9fa8c04b503
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: 8a0c44b54d304d4dfb2a205d41a5ac360e483209229fa49e767f9eaa595434b291661e283110f3ee39a8fbc17a4ad2d82f90a6e4545ca4112852ee50a35aa8da
         | 
| 7 | 
            +
              data.tar.gz: 52bb16489dd97777f7ff2359be9014a2c55c7537b8d4449621eb95ef3b7f0030febcd06caa811d406db1fb24fcc884d22c7460a36a94255133ce261a2bbeb68d
         | 
    
        data/CHANGELOG.md
    CHANGED
    
    | @@ -1,3 +1,15 @@ | |
| 1 | 
            +
            ## 0.12.0 (2021-05-15)
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            * Include total view render time in logs for Rodauth requests (@janko)
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            * Instrument redirects (@janko)
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            * Instrument Rodauth requests on `action_controller` namespace (@janko)
         | 
| 8 | 
            +
             | 
| 9 | 
            +
            * Update templates for Boostrap 5 compatibility (@janko)
         | 
| 10 | 
            +
             | 
| 11 | 
            +
            * Log request parameters for Rodauth requests (@janko)
         | 
| 12 | 
            +
             | 
| 1 13 | 
             
            ## 0.11.0 (2021-05-06)
         | 
| 2 14 |  | 
| 3 15 | 
             
            * Add controller-like logging for requests to Rodauth endpoints (@janko)
         | 
    
        data/README.md
    CHANGED
    
    | @@ -61,7 +61,7 @@ documentation][hmac] for instructions on how to safely transition, or just set | |
| 61 61 | 
             
            Add the gem to your Gemfile:
         | 
| 62 62 |  | 
| 63 63 | 
             
            ```rb
         | 
| 64 | 
            -
            gem "rodauth-rails", "~> 0. | 
| 64 | 
            +
            gem "rodauth-rails", "~> 0.12"
         | 
| 65 65 |  | 
| 66 66 | 
             
            # gem "jwt",      require: false # for JWT feature
         | 
| 67 67 | 
             
            # gem "rotp",     require: false # for OTP feature
         | 
| @@ -86,132 +86,22 @@ $ rails generate rodauth:install --jwt # token authentication via the "Authoriza | |
| 86 86 | 
             
            $ bundle add jwt
         | 
| 87 87 | 
             
            ```
         | 
| 88 88 |  | 
| 89 | 
            -
             | 
| 89 | 
            +
            This generator will create a Rodauth app with common authentication features
         | 
| 90 | 
            +
            enabled, a database migration with tables required by those features, a mailer
         | 
| 91 | 
            +
            with default templates, and a few other files.
         | 
| 90 92 |  | 
| 91 | 
            -
             | 
| 92 | 
            -
             | 
| 93 | 
            -
            * Sequel initializer at `config/initializers/sequel.rb` for ActiveRecord integration
         | 
| 94 | 
            -
            * Rodauth app at `app/lib/rodauth_app.rb`
         | 
| 95 | 
            -
            * Rodauth controller at `app/controllers/rodauth_controller.rb`
         | 
| 96 | 
            -
            * Account model at `app/models/account.rb`
         | 
| 97 | 
            -
            * Rodauth mailer at `app/mailers/rodauth_mailer.rb` with views
         | 
| 93 | 
            +
            Feel free to remove any features you don't need, along with their corresponding
         | 
| 94 | 
            +
            tables. Afterwards, run the migration:
         | 
| 98 95 |  | 
| 99 | 
            -
             | 
| 100 | 
            -
             | 
| 101 | 
            -
            The migration file creates tables required by Rodauth. You're encouraged to
         | 
| 102 | 
            -
            review the migration, and modify it to only create tables for features you
         | 
| 103 | 
            -
            intend to use.
         | 
| 104 | 
            -
             | 
| 105 | 
            -
            ```rb
         | 
| 106 | 
            -
            # db/migrate/*_create_rodauth.rb
         | 
| 107 | 
            -
            class CreateRodauth < ActiveRecord::Migration
         | 
| 108 | 
            -
              def change
         | 
| 109 | 
            -
                create_table :accounts do |t| ... end
         | 
| 110 | 
            -
                create_table :account_password_hashes do |t| ... end
         | 
| 111 | 
            -
                create_table :account_password_reset_keys do |t| ... end
         | 
| 112 | 
            -
                create_table :account_verification_keys do |t| ... end
         | 
| 113 | 
            -
                create_table :account_login_change_keys do |t| ... end
         | 
| 114 | 
            -
                create_table :account_remember_keys do |t| ... end
         | 
| 115 | 
            -
              end
         | 
| 116 | 
            -
            end
         | 
| 117 | 
            -
            ```
         | 
| 118 | 
            -
             | 
| 119 | 
            -
            Once you're done, you can run the migration:
         | 
| 120 | 
            -
             | 
| 121 | 
            -
            ```
         | 
| 96 | 
            +
            ```sh
         | 
| 122 97 | 
             
            $ rails db:migrate
         | 
| 123 98 | 
             
            ```
         | 
| 124 99 |  | 
| 125 | 
            -
            ### Rodauth initializer
         | 
| 126 | 
            -
             | 
| 127 | 
            -
            The Rodauth initializer assigns the constant for your Rodauth app, which will
         | 
| 128 | 
            -
            be called by the Rack middleware that's added in front of your Rails router.
         | 
| 129 | 
            -
             | 
| 130 | 
            -
            ```rb
         | 
| 131 | 
            -
            # config/initializers/rodauth.rb
         | 
| 132 | 
            -
            Rodauth::Rails.configure do |config|
         | 
| 133 | 
            -
              config.app = "RodauthApp"
         | 
| 134 | 
            -
            end
         | 
| 135 | 
            -
            ```
         | 
| 136 | 
            -
             | 
| 137 | 
            -
            ### Sequel initializer
         | 
| 138 | 
            -
             | 
| 139 | 
            -
            Rodauth uses [Sequel] for database interaction. If you're using ActiveRecord,
         | 
| 140 | 
            -
            an additional initializer will be created which configures Sequel to use the
         | 
| 141 | 
            -
            ActiveRecord connection.
         | 
| 142 | 
            -
             | 
| 143 | 
            -
            ```rb
         | 
| 144 | 
            -
            # config/initializers/sequel.rb
         | 
| 145 | 
            -
            require "sequel/core"
         | 
| 146 | 
            -
             | 
| 147 | 
            -
            # initialize Sequel and have it reuse Active Record's database connection
         | 
| 148 | 
            -
            DB = Sequel.connect("postgresql://", extensions: :activerecord_connection)
         | 
| 149 | 
            -
            ```
         | 
| 150 | 
            -
             | 
| 151 | 
            -
            ### Rodauth app
         | 
| 152 | 
            -
             | 
| 153 | 
            -
            Your Rodauth app is created in the `app/lib/` directory, and comes with a
         | 
| 154 | 
            -
            default set of authentication features enabled, as well as extensive examples
         | 
| 155 | 
            -
            on ways you can configure authentication behaviour.
         | 
| 156 | 
            -
             | 
| 157 | 
            -
            ```rb
         | 
| 158 | 
            -
            # app/lib/rodauth_app.rb
         | 
| 159 | 
            -
            class RodauthApp < Rodauth::Rails::App
         | 
| 160 | 
            -
              configure do
         | 
| 161 | 
            -
                # authentication configuration
         | 
| 162 | 
            -
              end
         | 
| 163 | 
            -
             | 
| 164 | 
            -
              route do |r|
         | 
| 165 | 
            -
                # request handling
         | 
| 166 | 
            -
              end
         | 
| 167 | 
            -
            end
         | 
| 168 | 
            -
            ```
         | 
| 169 | 
            -
             | 
| 170 | 
            -
            ### Controller
         | 
| 171 | 
            -
             | 
| 172 | 
            -
            Your Rodauth app will by default use `RodauthController` for view rendering,
         | 
| 173 | 
            -
            CSRF protection, and running controller callbacks and rescue handlers around
         | 
| 174 | 
            -
            Rodauth actions.
         | 
| 175 | 
            -
             | 
| 176 | 
            -
            ```rb
         | 
| 177 | 
            -
            # app/controllers/rodauth_controller.rb
         | 
| 178 | 
            -
            class RodauthController < ApplicationController
         | 
| 179 | 
            -
            end
         | 
| 180 | 
            -
            ```
         | 
| 181 | 
            -
             | 
| 182 | 
            -
            ### Account model
         | 
| 183 | 
            -
             | 
| 184 | 
            -
            Rodauth stores user accounts in the `accounts` table, so the generator will
         | 
| 185 | 
            -
            also create an `Account` model for custom use.
         | 
| 186 | 
            -
             | 
| 187 | 
            -
            ```rb
         | 
| 188 | 
            -
            # app/models/account.rb
         | 
| 189 | 
            -
            class Account < ApplicationRecord
         | 
| 190 | 
            -
            end
         | 
| 191 | 
            -
            ```
         | 
| 192 | 
            -
             | 
| 193 | 
            -
            ### Rodauth mailer
         | 
| 194 | 
            -
             | 
| 195 | 
            -
            The default Rodauth app is configured to use `RodauthMailer` mailer
         | 
| 196 | 
            -
            for sending authentication emails.
         | 
| 197 | 
            -
             | 
| 198 | 
            -
            ```rb
         | 
| 199 | 
            -
            # app/mailers/rodauth_mailer.rb
         | 
| 200 | 
            -
            class RodauthMailer < ApplicationMailer
         | 
| 201 | 
            -
              def verify_account(recipient, email_link) ... end
         | 
| 202 | 
            -
              def reset_password(recipient, email_link) ... end
         | 
| 203 | 
            -
              def verify_login_change(recipient, old_login, new_login, email_link) ... end
         | 
| 204 | 
            -
              def password_changed(recipient) ... end
         | 
| 205 | 
            -
              # def email_auth(recipient, email_link) ... end
         | 
| 206 | 
            -
              # def unlock_account(recipient, email_link) ... end
         | 
| 207 | 
            -
            end
         | 
| 208 | 
            -
            ```
         | 
| 209 | 
            -
             | 
| 210 100 | 
             
            ## Usage
         | 
| 211 101 |  | 
| 212 102 | 
             
            ### Routes
         | 
| 213 103 |  | 
| 214 | 
            -
             | 
| 104 | 
            +
            You can see the list of routes our Rodauth middleware handles:
         | 
| 215 105 |  | 
| 216 106 | 
             
            ```sh
         | 
| 217 107 | 
             
            $ rails rodauth:routes
         | 
| @@ -233,7 +123,7 @@ Routes handled by RodauthApp: | |
| 233 123 | 
             
              /close-account           rodauth.close_account_path
         | 
| 234 124 | 
             
            ```
         | 
| 235 125 |  | 
| 236 | 
            -
            Using this information,  | 
| 126 | 
            +
            Using this information, you can add some basic authentication links to your
         | 
| 237 127 | 
             
            navigation header:
         | 
| 238 128 |  | 
| 239 129 | 
             
            ```erb
         | 
| @@ -264,7 +154,7 @@ end | |
| 264 154 |  | 
| 265 155 | 
             
            ### Current account
         | 
| 266 156 |  | 
| 267 | 
            -
            To be able to fetch currently authenticated account,  | 
| 157 | 
            +
            To be able to fetch currently authenticated account, you can define a
         | 
| 268 158 | 
             
            `#current_account` method that fetches the account id from session and
         | 
| 269 159 | 
             
            retrieves the corresponding account record:
         | 
| 270 160 |  | 
| @@ -281,11 +171,11 @@ class ApplicationController < ActionController::Base | |
| 281 171 | 
             
                rodauth.logout
         | 
| 282 172 | 
             
                rodauth.login_required
         | 
| 283 173 | 
             
              end
         | 
| 284 | 
            -
              helper_method :current_account # skip if inheriting from ActionController | 
| 174 | 
            +
              helper_method :current_account # skip if inheriting from ActionController::API
         | 
| 285 175 | 
             
            end
         | 
| 286 176 | 
             
            ```
         | 
| 287 177 |  | 
| 288 | 
            -
            This allows  | 
| 178 | 
            +
            This allows you to access the current account in controllers and views:
         | 
| 289 179 |  | 
| 290 180 | 
             
            ```erb
         | 
| 291 181 | 
             
            <p>Authenticated as: <%= current_account.email %></p>
         | 
| @@ -293,9 +183,9 @@ This allows us to access the current account in controllers and views: | |
| 293 183 |  | 
| 294 184 | 
             
            ### Requiring authentication
         | 
| 295 185 |  | 
| 296 | 
            -
             | 
| 297 | 
            -
            redirecting the user to the login page if they're not logged in.  | 
| 298 | 
            -
            in  | 
| 186 | 
            +
            You'll likely want to require authentication for certain parts of your app,
         | 
| 187 | 
            +
            redirecting the user to the login page if they're not logged in. You can do this
         | 
| 188 | 
            +
            in your Rodauth app's routing block, which helps keep the authentication logic
         | 
| 299 189 | 
             
            encapsulated:
         | 
| 300 190 |  | 
| 301 191 | 
             
            ```rb
         | 
| @@ -314,7 +204,7 @@ class RodauthApp < Rodauth::Rails::App | |
| 314 204 | 
             
            end
         | 
| 315 205 | 
             
            ```
         | 
| 316 206 |  | 
| 317 | 
            -
             | 
| 207 | 
            +
            You can also require authentication at the controller layer:
         | 
| 318 208 |  | 
| 319 209 | 
             
            ```rb
         | 
| 320 210 | 
             
            # app/controllers/application_controller.rb
         | 
| @@ -341,8 +231,8 @@ end | |
| 341 231 |  | 
| 342 232 | 
             
            #### Routing constraints
         | 
| 343 233 |  | 
| 344 | 
            -
             | 
| 345 | 
            -
             | 
| 234 | 
            +
            In some cases it makes sense to require authentication at the Rails router
         | 
| 235 | 
            +
            level. You can do this via the built-in `authenticated` routing constraint:
         | 
| 346 236 |  | 
| 347 237 | 
             
            ```rb
         | 
| 348 238 | 
             
            # config/routes.rb
         | 
| @@ -404,7 +294,7 @@ This will generate views for the default set of Rodauth features into the | |
| 404 294 | 
             
            `RodauthController`.
         | 
| 405 295 |  | 
| 406 296 | 
             
            You can pass a list of Rodauth features to the generator to create views for
         | 
| 407 | 
            -
            these features (this will not remove any existing views):
         | 
| 297 | 
            +
            these features (this will not remove or overwrite any existing views):
         | 
| 408 298 |  | 
| 409 299 | 
             
            ```sh
         | 
| 410 300 | 
             
            $ rails generate rodauth:views login create_account lockout otp
         | 
| @@ -546,7 +436,7 @@ end | |
| 546 436 | 
             
            ### Multiple configurations
         | 
| 547 437 |  | 
| 548 438 | 
             
            If you need to handle multiple types of accounts that require different
         | 
| 549 | 
            -
            authentication logic, you can create  | 
| 439 | 
            +
            authentication logic, you can create additional configurations for them:
         | 
| 550 440 |  | 
| 551 441 | 
             
            ```rb
         | 
| 552 442 | 
             
            # app/lib/rodauth_app.rb
         | 
| @@ -656,8 +546,8 @@ class RodauthAdmin < RodauthBase # inherit common settings | |
| 656 546 | 
             
            end
         | 
| 657 547 | 
             
            ```
         | 
| 658 548 |  | 
| 659 | 
            -
            Another benefit is that you can define custom methods | 
| 660 | 
            -
            instead of  | 
| 549 | 
            +
            Another benefit of explicit classes is that you can define custom methods
         | 
| 550 | 
            +
            directly at the class level instead of inside an `auth_class_eval`:
         | 
| 661 551 |  | 
| 662 552 | 
             
            ```rb
         | 
| 663 553 | 
             
            # app/lib/rodauth_admin.rb
         | 
| @@ -722,7 +612,7 @@ rodauth.setup_account_verification | |
| 722 612 | 
             
            rodauth.close_account
         | 
| 723 613 | 
             
            ```
         | 
| 724 614 |  | 
| 725 | 
            -
            This Rodauth instance will be initialized with basic Rack env that allows  | 
| 615 | 
            +
            This Rodauth instance will be initialized with basic Rack env that allows it
         | 
| 726 616 | 
             
            to generate URLs, using `config.action_mailer.default_url_options` options.
         | 
| 727 617 |  | 
| 728 618 | 
             
            ## How it works
         | 
| @@ -834,7 +724,7 @@ class RodauthApp < Rodauth::Rails::App | |
| 834 724 | 
             
              configure do
         | 
| 835 725 | 
             
                # ...
         | 
| 836 726 | 
             
                enable :json
         | 
| 837 | 
            -
                only_json? true # accept only JSON requests
         | 
| 727 | 
            +
                only_json? true # accept only JSON requests (optional)
         | 
| 838 728 | 
             
                # ...
         | 
| 839 729 | 
             
              end
         | 
| 840 730 | 
             
            end
         | 
| @@ -855,7 +745,7 @@ class RodauthApp < Rodauth::Rails::App | |
| 855 745 | 
             
                # ...
         | 
| 856 746 | 
             
                enable :jwt
         | 
| 857 747 | 
             
                jwt_secret "<YOUR_SECRET_KEY>" # store the JWT secret in a safe place
         | 
| 858 | 
            -
                only_json? true # accept only JSON requests
         | 
| 748 | 
            +
                only_json? true # accept only JSON requests (optional)
         | 
| 859 749 | 
             
                # ...
         | 
| 860 750 | 
             
              end
         | 
| 861 751 | 
             
            end
         | 
| @@ -935,7 +825,8 @@ end | |
| 935 825 | 
             
            <%= link_to "Login via Facebook", "/auth/facebook" %>
         | 
| 936 826 | 
             
            ```
         | 
| 937 827 |  | 
| 938 | 
            -
             | 
| 828 | 
            +
            Finally, let's implement the OmniAuth callback endpoint on our Rodauth
         | 
| 829 | 
            +
            controller:
         | 
| 939 830 |  | 
| 940 831 | 
             
            ```rb
         | 
| 941 832 | 
             
            # config/routes.rb
         | 
| @@ -988,11 +879,8 @@ end | |
| 988 879 |  | 
| 989 880 | 
             
            ## Configuring
         | 
| 990 881 |  | 
| 991 | 
            -
             | 
| 992 | 
            -
             | 
| 993 | 
            -
             | 
| 994 | 
            -
            The `rails` feature rodauth-rails loads is customizable as well, here is the
         | 
| 995 | 
            -
            list of its configuration methods:
         | 
| 882 | 
            +
            The `rails` feature rodauth-rails loads provides the following configuration
         | 
| 883 | 
            +
            methods:
         | 
| 996 884 |  | 
| 997 885 | 
             
            | Name                        | Description                                                        |
         | 
| 998 886 | 
             
            | :----                       | :----------                                                        |
         | 
| @@ -1019,12 +907,16 @@ Rodauth::Rails.configure do |config| | |
| 1019 907 | 
             
            end
         | 
| 1020 908 | 
             
            ```
         | 
| 1021 909 |  | 
| 910 | 
            +
            For the list of configuration methods provided by Rodauth, see the [feature
         | 
| 911 | 
            +
            documentation].
         | 
| 912 | 
            +
             | 
| 1022 913 | 
             
            ## Custom extensions
         | 
| 1023 914 |  | 
| 1024 915 | 
             
            When developing custom extensions for Rodauth inside your Rails project, it's
         | 
| 1025 | 
            -
            better to use plain modules  | 
| 1026 | 
            -
            feature design doesn't yet  | 
| 1027 | 
            -
             | 
| 916 | 
            +
            probably better to use plain modules, at least in the beginning, as Rodauth
         | 
| 917 | 
            +
            feature design doesn't yet work well with Zeitwerk reloading.
         | 
| 918 | 
            +
             | 
| 919 | 
            +
            Here is an example of an LDAP authentication extension that uses the
         | 
| 1028 920 | 
             
            [simple_ldap_authenticator] gem.
         | 
| 1029 921 |  | 
| 1030 922 | 
             
            ```rb
         | 
| @@ -1,4 +1,4 @@ | |
| 1 | 
            -
            <div class="form-group">
         | 
| 1 | 
            +
            <div class="form-group mb-3">
         | 
| 2 2 | 
             
              <div class="form-check">
         | 
| 3 3 | 
             
                <%%= check_box_tag rodauth.global_logout_param, "t", false, id: "global-logout", class: "form-check-input" %>
         | 
| 4 4 | 
             
                <%%= label_tag "global-logout", "Logout all Logged In Sessons?", class: "form-check-label" %>
         | 
| @@ -1,4 +1,4 @@ | |
| 1 | 
            -
            <div class="form-group">
         | 
| 2 | 
            -
              <%%= label_tag "login-confirm", "Confirm Login" %>
         | 
| 1 | 
            +
            <div class="form-group mb-3">
         | 
| 2 | 
            +
              <%%= label_tag "login-confirm", "Confirm Login", class: "form-label" %>
         | 
| 3 3 | 
             
              <%%= render "field", name: rodauth.login_confirm_param, id: "login-confirm", type: :email, autocomplete: "email" %>
         | 
| 4 4 | 
             
            </div>
         | 
| @@ -1,4 +1,4 @@ | |
| 1 | 
            -
            <div class="form-group">
         | 
| 2 | 
            -
              <%%= label_tag "login", "Login" %>
         | 
| 1 | 
            +
            <div class="form-group mb-3">
         | 
| 2 | 
            +
              <%%= label_tag "login", "Login", class: "form-label" %>
         | 
| 3 3 | 
             
              <%%= email_field_tag rodauth.login_param, params[rodauth.login_param], id: "login", readonly: true, class: "form-control-plaintext" %>
         | 
| 4 4 | 
             
            </div>
         | 
| @@ -1,4 +1,4 @@ | |
| 1 | 
            -
            <div class="form-group">
         | 
| 2 | 
            -
              <%%= label_tag "new-password", "New Password" %>
         | 
| 1 | 
            +
            <div class="form-group mb-3">
         | 
| 2 | 
            +
              <%%= label_tag "new-password", "New Password", class: "form-label" %>
         | 
| 3 3 | 
             
              <%%= render "field", name: rodauth.new_password_param, id: "new-password", type: "password", value: "", autocomplete: "new-password" %>
         | 
| 4 4 | 
             
            </div>
         | 
| @@ -1,5 +1,5 @@ | |
| 1 | 
            -
            <div class="form-group">
         | 
| 2 | 
            -
              <%%= label_tag "otp-auth-code", "Authentication Code" %>
         | 
| 1 | 
            +
            <div class="form-group mb-3">
         | 
| 2 | 
            +
              <%%= label_tag "otp-auth-code", "Authentication Code", class: "form-label" %>
         | 
| 3 3 | 
             
              <div class="row">
         | 
| 4 4 | 
             
                <div class="col-sm-3">
         | 
| 5 5 | 
             
                  <%%= render "field", name: rodauth.otp_auth_param, id: "otp-auth-code", value: "", autocomplete: "off", inputmode: "numeric" %>
         | 
| @@ -1,4 +1,4 @@ | |
| 1 | 
            -
            <div class="form-group">
         | 
| 2 | 
            -
              <%%= label_tag "password-confirm", "Confirm Password" %>
         | 
| 1 | 
            +
            <div class="form-group mb-3">
         | 
| 2 | 
            +
              <%%= label_tag "password-confirm", "Confirm Password", class: "form-label" %>
         | 
| 3 3 | 
             
              <%%= render "field", name: rodauth.password_confirm_param, id: "password-confirm", type: :password, value: "", autocomplete: "new-password" %>
         | 
| 4 4 | 
             
            </div>
         | 
| @@ -1,4 +1,4 @@ | |
| 1 | 
            -
            <div class="form-group">
         | 
| 2 | 
            -
              <%%= label_tag "password", "Password" %>
         | 
| 1 | 
            +
            <div class="form-group mb-3">
         | 
| 2 | 
            +
              <%%= label_tag "password", "Password", class: "form-label" %>
         | 
| 3 3 | 
             
              <%%= render "field", name: rodauth.password_param, id: "password", type: :password, value: "", autocomplete: rodauth.password_field_autocomplete_value %>
         | 
| 4 4 | 
             
            </div>
         | 
| @@ -1,4 +1,4 @@ | |
| 1 | 
            -
            <div class="form-group">
         | 
| 2 | 
            -
              <%%= label_tag "recovery_code", "Recovery Code" %>
         | 
| 1 | 
            +
            <div class="form-group mb-3">
         | 
| 2 | 
            +
              <%%= label_tag "recovery_code", "Recovery Code", class: "form-label" %>
         | 
| 3 3 | 
             
              <%%= render "field", name: rodauth.recovery_codes_param, id: "recovery_code", value: "", autocomplete: "off" %>
         | 
| 4 4 | 
             
            </div>
         | 
| @@ -1,5 +1,5 @@ | |
| 1 | 
            -
            <div class="form-group">
         | 
| 2 | 
            -
              <%%= label_tag "sms-code", "SMS Code" %>
         | 
| 1 | 
            +
            <div class="form-group mb-3">
         | 
| 2 | 
            +
              <%%= label_tag "sms-code", "SMS Code", class: "form-label" %>
         | 
| 3 3 | 
             
              <div class="row">
         | 
| 4 4 | 
             
                <div class="col-sm-3">
         | 
| 5 5 | 
             
                  <%%= render "field", name: rodauth.sms_code_param, id: "sms-code", value: "", autocomplete: "one-time-code", inputmode: "numeric" %>
         | 
| @@ -1,5 +1,5 @@ | |
| 1 | 
            -
            <div class="form-group">
         | 
| 2 | 
            -
              <%%= label_tag "sms-phone", "Phone Number" %>
         | 
| 1 | 
            +
            <div class="form-group mb-3">
         | 
| 2 | 
            +
              <%%= label_tag "sms-phone", "Phone Number", class: "form-label" %>
         | 
| 3 3 | 
             
              <div class="row">
         | 
| 4 4 | 
             
                <div class="col-sm-3">
         | 
| 5 5 | 
             
                  <%%= render "field", name: rodauth.sms_phone_param, id: "sms-phone", type: :tel, autocomplete: "tel" %>
         | 
| @@ -2,14 +2,14 @@ | |
| 2 2 | 
             
              <%%= hidden_field_tag rodauth.otp_setup_param, rodauth.otp_user_key, id: "otp-key" %>
         | 
| 3 3 | 
             
              <%%= hidden_field_tag rodauth.otp_setup_raw_param, rodauth.otp_key, id: "otp-hmac-secret" if rodauth.otp_keys_use_hmac? %>
         | 
| 4 4 |  | 
| 5 | 
            -
              <div class="form-group">
         | 
| 5 | 
            +
              <div class="form-group mb-3">
         | 
| 6 6 | 
             
                <p>Secret: <%%= rodauth.otp_user_key %></p>
         | 
| 7 7 | 
             
                <p>Provisioning URL: <%%= rodauth.otp_provisioning_uri %></p>
         | 
| 8 8 | 
             
              </div>
         | 
| 9 9 |  | 
| 10 10 | 
             
              <div class="row">
         | 
| 11 11 | 
             
                <div class="col-lg-6 col-lg">
         | 
| 12 | 
            -
                  <div class="form-group">
         | 
| 12 | 
            +
                  <div class="form-group mb-3">
         | 
| 13 13 | 
             
                    <p><%%= rodauth.otp_qr_code.html_safe %></p>
         | 
| 14 14 | 
             
                  </div>
         | 
| 15 15 | 
             
                </div>
         | 
| @@ -1,5 +1,5 @@ | |
| 1 1 | 
             
            <%%= form_tag rodauth.remember_path, method: :post do %>
         | 
| 2 | 
            -
              <fieldset class="form-group">
         | 
| 2 | 
            +
              <fieldset class="form-group mb-3">
         | 
| 3 3 | 
             
                <div class="form-check">
         | 
| 4 4 | 
             
                  <%%= radio_button_tag rodauth.remember_param, rodauth.remember_remember_param_value, false, id: "remember-remember", class: "form-check-input" %>
         | 
| 5 5 | 
             
                  <%%= label_tag "remember-remember", "Remember Me", class: "form-check-label" %>
         | 
| @@ -1,6 +1,6 @@ | |
| 1 1 | 
             
            <%%= form_tag rodauth.webauthn_remove_path, method: :post, id: "webauthn-remove-form" do %>
         | 
| 2 2 | 
             
              <%%= render "password_field" if rodauth.two_factor_modifications_require_password? %>
         | 
| 3 | 
            -
              <fieldset class="form-group">
         | 
| 3 | 
            +
              <fieldset class="form-group mb-3">
         | 
| 4 4 | 
             
                <%% (usage = rodauth.account_webauthn_usage).each do |id, last_use| %>
         | 
| 5 5 | 
             
                  <div class="form-check">
         | 
| 6 6 | 
             
                    <%%= render "field", name: rodauth.webauthn_remove_param, id: "webauthn-remove-#{id}", type: :radio, class: "form-check-input", skip_error_message: true, value: id, required: false %>
         | 
| @@ -1,234 +1,21 @@ | |
| 1 1 | 
             
            module Rodauth
         | 
| 2 2 | 
             
              Feature.define(:rails) do
         | 
| 3 | 
            -
                 | 
| 4 | 
            -
             | 
| 5 | 
            -
                 | 
| 6 | 
            -
             | 
| 7 | 
            -
             | 
| 8 | 
            -
             | 
| 9 | 
            -
             | 
| 10 | 
            -
             | 
| 11 | 
            -
             | 
| 12 | 
            -
             | 
| 13 | 
            -
             | 
| 14 | 
            -
             | 
| 15 | 
            -
                 | 
| 16 | 
            -
             | 
| 17 | 
            -
                 | 
| 18 | 
            -
                 | 
| 19 | 
            -
                 | 
| 20 | 
            -
                  rails_render(action: page.tr("-", "_"), layout: true) ||
         | 
| 21 | 
            -
                    rails_render(html: super.html_safe, layout: true)
         | 
| 22 | 
            -
                end
         | 
| 23 | 
            -
             | 
| 24 | 
            -
                # Renders templates without layout. First tries to render a user-defined
         | 
| 25 | 
            -
                # template or partial, otherwise falls back to Rodauth's template.
         | 
| 26 | 
            -
                def render(page)
         | 
| 27 | 
            -
                  rails_render(partial: page.tr("-", "_"), layout: false) ||
         | 
| 28 | 
            -
                    rails_render(action: page.tr("-", "_"), layout: false) ||
         | 
| 29 | 
            -
                    super.html_safe
         | 
| 30 | 
            -
                end
         | 
| 31 | 
            -
             | 
| 32 | 
            -
                # Render Rails CSRF tags in Rodauth templates.
         | 
| 33 | 
            -
                def csrf_tag(*)
         | 
| 34 | 
            -
                  rails_csrf_tag
         | 
| 35 | 
            -
                end
         | 
| 36 | 
            -
             | 
| 37 | 
            -
                # Verify Rails' authenticity token.
         | 
| 38 | 
            -
                def check_csrf
         | 
| 39 | 
            -
                  rails_check_csrf!
         | 
| 40 | 
            -
                end
         | 
| 41 | 
            -
             | 
| 42 | 
            -
                # Have Rodauth call #check_csrf automatically.
         | 
| 43 | 
            -
                def check_csrf?
         | 
| 44 | 
            -
                  true
         | 
| 45 | 
            -
                end
         | 
| 46 | 
            -
             | 
| 47 | 
            -
                # Reset Rails session to protect from session fixation attacks.
         | 
| 48 | 
            -
                def clear_session
         | 
| 49 | 
            -
                  rails_controller_instance.reset_session
         | 
| 50 | 
            -
                end
         | 
| 51 | 
            -
             | 
| 52 | 
            -
                # Default the flash error key to Rails' default :alert.
         | 
| 53 | 
            -
                def flash_error_key
         | 
| 54 | 
            -
                  :alert
         | 
| 55 | 
            -
                end
         | 
| 56 | 
            -
             | 
| 57 | 
            -
                # Evaluates the block in context of a Rodauth controller instance.
         | 
| 58 | 
            -
                def rails_controller_eval(&block)
         | 
| 59 | 
            -
                  rails_controller_instance.instance_exec(&block)
         | 
| 60 | 
            -
                end
         | 
| 61 | 
            -
             | 
| 62 | 
            -
                def button(*)
         | 
| 63 | 
            -
                  super.html_safe
         | 
| 64 | 
            -
                end
         | 
| 65 | 
            -
             | 
| 66 | 
            -
                delegate :rails_routes, :rails_request, to: :scope
         | 
| 67 | 
            -
             | 
| 68 | 
            -
                private
         | 
| 69 | 
            -
             | 
| 70 | 
            -
                # Runs controller callbacks and rescue handlers around Rodauth actions.
         | 
| 71 | 
            -
                def _around_rodauth(&block)
         | 
| 72 | 
            -
                  result = nil
         | 
| 73 | 
            -
             | 
| 74 | 
            -
                  rails_instrument_request do
         | 
| 75 | 
            -
                    rails_controller_rescue do
         | 
| 76 | 
            -
                      rails_controller_callbacks do
         | 
| 77 | 
            -
                        result = catch(:halt) { super(&block) }
         | 
| 78 | 
            -
                      end
         | 
| 79 | 
            -
                    end
         | 
| 80 | 
            -
             | 
| 81 | 
            -
                    result = handle_rails_controller_response(result)
         | 
| 82 | 
            -
                  end
         | 
| 83 | 
            -
             | 
| 84 | 
            -
                  throw :halt, result if result
         | 
| 85 | 
            -
                end
         | 
| 86 | 
            -
             | 
| 87 | 
            -
                # Handles controller rendering a response or setting response headers.
         | 
| 88 | 
            -
                def handle_rails_controller_response(result)
         | 
| 89 | 
            -
                  if rails_controller_instance.performed?
         | 
| 90 | 
            -
                    rails_controller_response
         | 
| 91 | 
            -
                  elsif result
         | 
| 92 | 
            -
                    result[1].merge!(rails_controller_instance.response.headers)
         | 
| 93 | 
            -
                    result
         | 
| 94 | 
            -
                  end
         | 
| 95 | 
            -
                end
         | 
| 96 | 
            -
             | 
| 97 | 
            -
                # Runs any #(before|around|after)_action controller callbacks.
         | 
| 98 | 
            -
                def rails_controller_callbacks
         | 
| 99 | 
            -
                  # don't verify CSRF token as part of callbacks, Rodauth will do that
         | 
| 100 | 
            -
                  rails_controller_forgery_protection { false }
         | 
| 101 | 
            -
             | 
| 102 | 
            -
                  rails_controller_instance.run_callbacks(:process_action) do
         | 
| 103 | 
            -
                    # turn the setting back to default so that form tags generate CSRF tags
         | 
| 104 | 
            -
                    rails_controller_forgery_protection { rails_controller.allow_forgery_protection }
         | 
| 105 | 
            -
             | 
| 106 | 
            -
                    yield
         | 
| 107 | 
            -
                  end
         | 
| 108 | 
            -
                end
         | 
| 109 | 
            -
             | 
| 110 | 
            -
                # Runs any registered #rescue_from controller handlers.
         | 
| 111 | 
            -
                def rails_controller_rescue
         | 
| 112 | 
            -
                  yield
         | 
| 113 | 
            -
                rescue Exception => exception
         | 
| 114 | 
            -
                  rails_controller_instance.rescue_with_handler(exception) || raise
         | 
| 115 | 
            -
             | 
| 116 | 
            -
                  unless rails_controller_instance.performed?
         | 
| 117 | 
            -
                    raise Rodauth::Rails::Error, "rescue_from handler didn't write any response"
         | 
| 118 | 
            -
                  end
         | 
| 119 | 
            -
                end
         | 
| 120 | 
            -
             | 
| 121 | 
            -
                def rails_instrument_request
         | 
| 122 | 
            -
                  ActiveSupport::Notifications.instrument("start_processing.rodauth", rodauth: self)
         | 
| 123 | 
            -
                  ActiveSupport::Notifications.instrument("process_request.rodauth", rodauth: self) do |payload|
         | 
| 124 | 
            -
                    begin
         | 
| 125 | 
            -
                      status, headers, body = yield
         | 
| 126 | 
            -
                      payload[:status] = status || 404
         | 
| 127 | 
            -
                      payload[:headers] = headers
         | 
| 128 | 
            -
                      payload[:body] = body
         | 
| 129 | 
            -
                    ensure
         | 
| 130 | 
            -
                      rails_controller_instance.send(:append_info_to_payload, payload)
         | 
| 131 | 
            -
                    end
         | 
| 132 | 
            -
                  end
         | 
| 133 | 
            -
                end
         | 
| 134 | 
            -
             | 
| 135 | 
            -
                # Returns Roda response from controller response if set.
         | 
| 136 | 
            -
                def rails_controller_response
         | 
| 137 | 
            -
                  controller_response = rails_controller_instance.response
         | 
| 138 | 
            -
             | 
| 139 | 
            -
                  response.status = controller_response.status
         | 
| 140 | 
            -
                  response.headers.merge! controller_response.headers
         | 
| 141 | 
            -
                  response.write controller_response.body
         | 
| 142 | 
            -
             | 
| 143 | 
            -
                  response.finish
         | 
| 144 | 
            -
                end
         | 
| 145 | 
            -
             | 
| 146 | 
            -
                # Create emails with ActionMailer which uses configured delivery method.
         | 
| 147 | 
            -
                def create_email_to(to, subject, body)
         | 
| 148 | 
            -
                  Mailer.create_email(to: to, from: email_from, subject: "#{email_subject_prefix}#{subject}", body: body)
         | 
| 149 | 
            -
                end
         | 
| 150 | 
            -
             | 
| 151 | 
            -
                # Delivers the given email.
         | 
| 152 | 
            -
                def send_email(email)
         | 
| 153 | 
            -
                  email.deliver_now
         | 
| 154 | 
            -
                end
         | 
| 155 | 
            -
             | 
| 156 | 
            -
                # Calls the Rails renderer, returning nil if a template is missing.
         | 
| 157 | 
            -
                def rails_render(*args)
         | 
| 158 | 
            -
                  return if rails_api_controller?
         | 
| 159 | 
            -
             | 
| 160 | 
            -
                  rails_controller_instance.render_to_string(*args)
         | 
| 161 | 
            -
                rescue ActionView::MissingTemplate
         | 
| 162 | 
            -
                  nil
         | 
| 163 | 
            -
                end
         | 
| 164 | 
            -
             | 
| 165 | 
            -
                # Calls the controller to verify the authenticity token.
         | 
| 166 | 
            -
                def rails_check_csrf!
         | 
| 167 | 
            -
                  rails_controller_instance.send(:verify_authenticity_token)
         | 
| 168 | 
            -
                end
         | 
| 169 | 
            -
             | 
| 170 | 
            -
                # Hidden tag with Rails CSRF token inserted into Rodauth templates.
         | 
| 171 | 
            -
                def rails_csrf_tag
         | 
| 172 | 
            -
                  %(<input type="hidden" name="#{rails_csrf_param}" value="#{rails_csrf_token}">)
         | 
| 173 | 
            -
                end
         | 
| 174 | 
            -
             | 
| 175 | 
            -
                # The request parameter under which to send the Rails CSRF token.
         | 
| 176 | 
            -
                def rails_csrf_param
         | 
| 177 | 
            -
                  rails_controller.request_forgery_protection_token
         | 
| 178 | 
            -
                end
         | 
| 179 | 
            -
             | 
| 180 | 
            -
                # The Rails CSRF token value inserted into Rodauth templates.
         | 
| 181 | 
            -
                def rails_csrf_token
         | 
| 182 | 
            -
                  rails_controller_instance.send(:form_authenticity_token)
         | 
| 183 | 
            -
                end
         | 
| 184 | 
            -
             | 
| 185 | 
            -
                # allows/disables forgery protection
         | 
| 186 | 
            -
                def rails_controller_forgery_protection(&value)
         | 
| 187 | 
            -
                  return if rails_api_controller?
         | 
| 188 | 
            -
             | 
| 189 | 
            -
                  rails_controller_instance.allow_forgery_protection = value.call
         | 
| 190 | 
            -
                end
         | 
| 191 | 
            -
             | 
| 192 | 
            -
                # Instances of the configured controller with current request's env hash.
         | 
| 193 | 
            -
                def _rails_controller_instance
         | 
| 194 | 
            -
                  controller = rails_controller.new
         | 
| 195 | 
            -
                  prepare_rails_controller(controller, rails_request)
         | 
| 196 | 
            -
                  controller
         | 
| 197 | 
            -
                end
         | 
| 198 | 
            -
             | 
| 199 | 
            -
                if ActionPack.version >= Gem::Version.new("5.0")
         | 
| 200 | 
            -
                  def prepare_rails_controller(controller, rails_request)
         | 
| 201 | 
            -
                    controller.set_request! rails_request
         | 
| 202 | 
            -
                    controller.set_response! rails_controller.make_response!(rails_request)
         | 
| 203 | 
            -
                  end
         | 
| 204 | 
            -
                else
         | 
| 205 | 
            -
                  def prepare_rails_controller(controller, rails_request)
         | 
| 206 | 
            -
                    controller.send(:set_response!, rails_request)
         | 
| 207 | 
            -
                    controller.instance_variable_set(:@_request, rails_request)
         | 
| 208 | 
            -
                  end
         | 
| 209 | 
            -
                end
         | 
| 210 | 
            -
             | 
| 211 | 
            -
                def rails_api_controller?
         | 
| 212 | 
            -
                  defined?(ActionController::API) && rails_controller <= ActionController::API
         | 
| 213 | 
            -
                end
         | 
| 214 | 
            -
             | 
| 215 | 
            -
                def rails_controller
         | 
| 216 | 
            -
                  if only_json? && Rodauth::Rails.api_only?
         | 
| 217 | 
            -
                    ActionController::API
         | 
| 218 | 
            -
                  else
         | 
| 219 | 
            -
                    ActionController::Base
         | 
| 220 | 
            -
                  end
         | 
| 221 | 
            -
                end
         | 
| 222 | 
            -
             | 
| 223 | 
            -
                # ActionMailer subclass for correct email delivering.
         | 
| 224 | 
            -
                class Mailer < ActionMailer::Base
         | 
| 225 | 
            -
                  def create_email(**options)
         | 
| 226 | 
            -
                    mail(**options)
         | 
| 227 | 
            -
                  end
         | 
| 228 | 
            -
                end
         | 
| 3 | 
            +
                # Assign feature and feature configuration to constants for introspection.
         | 
| 4 | 
            +
                Rodauth::Rails::Feature              = self
         | 
| 5 | 
            +
                Rodauth::Rails::FeatureConfiguration = self.configuration
         | 
| 6 | 
            +
             | 
| 7 | 
            +
                require "rodauth/rails/feature/base"
         | 
| 8 | 
            +
                require "rodauth/rails/feature/callbacks"
         | 
| 9 | 
            +
                require "rodauth/rails/feature/csrf"
         | 
| 10 | 
            +
                require "rodauth/rails/feature/render"
         | 
| 11 | 
            +
                require "rodauth/rails/feature/email"
         | 
| 12 | 
            +
                require "rodauth/rails/feature/instrumentation"
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                include Rodauth::Rails::Feature::Base
         | 
| 15 | 
            +
                include Rodauth::Rails::Feature::Callbacks
         | 
| 16 | 
            +
                include Rodauth::Rails::Feature::Csrf
         | 
| 17 | 
            +
                include Rodauth::Rails::Feature::Render
         | 
| 18 | 
            +
                include Rodauth::Rails::Feature::Email
         | 
| 19 | 
            +
                include Rodauth::Rails::Feature::Instrumentation
         | 
| 229 20 | 
             
              end
         | 
| 230 | 
            -
             | 
| 231 | 
            -
              # Assign feature and feature configuration to constants for introspection.
         | 
| 232 | 
            -
              Rails::Feature              = FEATURES[:rails]
         | 
| 233 | 
            -
              Rails::FeatureConfiguration = FEATURES[:rails].configuration
         | 
| 234 21 | 
             
            end
         | 
| @@ -0,0 +1,62 @@ | |
| 1 | 
            +
            module Rodauth
         | 
| 2 | 
            +
              module Rails
         | 
| 3 | 
            +
                module Feature
         | 
| 4 | 
            +
                  module Base
         | 
| 5 | 
            +
                    def self.included(feature)
         | 
| 6 | 
            +
                      feature.auth_methods :rails_controller
         | 
| 7 | 
            +
                      feature.auth_cached_method :rails_controller_instance
         | 
| 8 | 
            +
                    end
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                    # Reset Rails session to protect from session fixation attacks.
         | 
| 11 | 
            +
                    def clear_session
         | 
| 12 | 
            +
                      rails_controller_instance.reset_session
         | 
| 13 | 
            +
                    end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                    # Default the flash error key to Rails' default :alert.
         | 
| 16 | 
            +
                    def flash_error_key
         | 
| 17 | 
            +
                      :alert
         | 
| 18 | 
            +
                    end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                    # Evaluates the block in context of a Rodauth controller instance.
         | 
| 21 | 
            +
                    def rails_controller_eval(&block)
         | 
| 22 | 
            +
                      rails_controller_instance.instance_exec(&block)
         | 
| 23 | 
            +
                    end
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                    delegate :rails_routes, :rails_request, to: :scope
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                    private
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                    # Instances of the configured controller with current request's env hash.
         | 
| 30 | 
            +
                    def _rails_controller_instance
         | 
| 31 | 
            +
                      controller = rails_controller.new
         | 
| 32 | 
            +
                      prepare_rails_controller(controller, rails_request)
         | 
| 33 | 
            +
                      controller
         | 
| 34 | 
            +
                    end
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                    if ActionPack.version >= Gem::Version.new("5.0")
         | 
| 37 | 
            +
                      def prepare_rails_controller(controller, rails_request)
         | 
| 38 | 
            +
                        controller.set_request! rails_request
         | 
| 39 | 
            +
                        controller.set_response! rails_controller.make_response!(rails_request)
         | 
| 40 | 
            +
                      end
         | 
| 41 | 
            +
                    else
         | 
| 42 | 
            +
                      def prepare_rails_controller(controller, rails_request)
         | 
| 43 | 
            +
                        controller.send(:set_response!, rails_request)
         | 
| 44 | 
            +
                        controller.instance_variable_set(:@_request, rails_request)
         | 
| 45 | 
            +
                      end
         | 
| 46 | 
            +
                    end
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                    def rails_api_controller?
         | 
| 49 | 
            +
                      defined?(ActionController::API) && rails_controller <= ActionController::API
         | 
| 50 | 
            +
                    end
         | 
| 51 | 
            +
             | 
| 52 | 
            +
                    def rails_controller
         | 
| 53 | 
            +
                      if only_json? && Rodauth::Rails.api_only?
         | 
| 54 | 
            +
                        ActionController::API
         | 
| 55 | 
            +
                      else
         | 
| 56 | 
            +
                        ActionController::Base
         | 
| 57 | 
            +
                      end
         | 
| 58 | 
            +
                    end
         | 
| 59 | 
            +
                  end
         | 
| 60 | 
            +
                end
         | 
| 61 | 
            +
              end
         | 
| 62 | 
            +
            end
         | 
| @@ -0,0 +1,61 @@ | |
| 1 | 
            +
            module Rodauth
         | 
| 2 | 
            +
              module Rails
         | 
| 3 | 
            +
                module Feature
         | 
| 4 | 
            +
                  module Callbacks
         | 
| 5 | 
            +
                    private
         | 
| 6 | 
            +
             | 
| 7 | 
            +
                    # Runs controller callbacks and rescue handlers around Rodauth actions.
         | 
| 8 | 
            +
                    def _around_rodauth(&block)
         | 
| 9 | 
            +
                      result = nil
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                      rails_controller_rescue do
         | 
| 12 | 
            +
                        rails_controller_callbacks do
         | 
| 13 | 
            +
                          result = catch(:halt) { super(&block) }
         | 
| 14 | 
            +
                        end
         | 
| 15 | 
            +
                      end
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                      result = handle_rails_controller_response(result)
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                      throw :halt, result if result
         | 
| 20 | 
            +
                    end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                    # Runs any #(before|around|after)_action controller callbacks.
         | 
| 23 | 
            +
                    def rails_controller_callbacks(&block)
         | 
| 24 | 
            +
                      rails_controller_instance.run_callbacks(:process_action, &block)
         | 
| 25 | 
            +
                    end
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                    # Runs any registered #rescue_from controller handlers.
         | 
| 28 | 
            +
                    def rails_controller_rescue
         | 
| 29 | 
            +
                      yield
         | 
| 30 | 
            +
                    rescue Exception => exception
         | 
| 31 | 
            +
                      rails_controller_instance.rescue_with_handler(exception) || raise
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                      unless rails_controller_instance.performed?
         | 
| 34 | 
            +
                        raise Rodauth::Rails::Error, "rescue_from handler didn't write any response"
         | 
| 35 | 
            +
                      end
         | 
| 36 | 
            +
                    end
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                    # Handles controller rendering a response or setting response headers.
         | 
| 39 | 
            +
                    def handle_rails_controller_response(result)
         | 
| 40 | 
            +
                      if rails_controller_instance.performed?
         | 
| 41 | 
            +
                        rails_controller_response
         | 
| 42 | 
            +
                      elsif result
         | 
| 43 | 
            +
                        result[1].merge!(rails_controller_instance.response.headers)
         | 
| 44 | 
            +
                        result
         | 
| 45 | 
            +
                      end
         | 
| 46 | 
            +
                    end
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                    # Returns Roda response from controller response if set.
         | 
| 49 | 
            +
                    def rails_controller_response
         | 
| 50 | 
            +
                      controller_response = rails_controller_instance.response
         | 
| 51 | 
            +
             | 
| 52 | 
            +
                      response.status = controller_response.status
         | 
| 53 | 
            +
                      response.headers.merge! controller_response.headers
         | 
| 54 | 
            +
                      response.write controller_response.body
         | 
| 55 | 
            +
             | 
| 56 | 
            +
                      response.finish
         | 
| 57 | 
            +
                    end
         | 
| 58 | 
            +
                  end
         | 
| 59 | 
            +
                end
         | 
| 60 | 
            +
              end
         | 
| 61 | 
            +
            end
         | 
| @@ -0,0 +1,65 @@ | |
| 1 | 
            +
            module Rodauth
         | 
| 2 | 
            +
              module Rails
         | 
| 3 | 
            +
                module Feature
         | 
| 4 | 
            +
                  module Csrf
         | 
| 5 | 
            +
                    def self.included(feature)
         | 
| 6 | 
            +
                      feature.auth_methods(
         | 
| 7 | 
            +
                        :rails_csrf_tag,
         | 
| 8 | 
            +
                        :rails_csrf_param,
         | 
| 9 | 
            +
                        :rails_csrf_token,
         | 
| 10 | 
            +
                        :rails_check_csrf!,
         | 
| 11 | 
            +
                      )
         | 
| 12 | 
            +
                    end
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                    # Render Rails CSRF tags in Rodauth templates.
         | 
| 15 | 
            +
                    def csrf_tag(*)
         | 
| 16 | 
            +
                      rails_csrf_tag
         | 
| 17 | 
            +
                    end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                    # Verify Rails' authenticity token.
         | 
| 20 | 
            +
                    def check_csrf
         | 
| 21 | 
            +
                      rails_check_csrf!
         | 
| 22 | 
            +
                    end
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                    # Have Rodauth call #check_csrf automatically.
         | 
| 25 | 
            +
                    def check_csrf?
         | 
| 26 | 
            +
                      true
         | 
| 27 | 
            +
                    end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                    private
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                    def rails_controller_callbacks
         | 
| 32 | 
            +
                      return super if rails_api_controller?
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                      # don't verify CSRF token as part of callbacks, Rodauth will do that
         | 
| 35 | 
            +
                      rails_controller_instance.allow_forgery_protection = false
         | 
| 36 | 
            +
                      super do
         | 
| 37 | 
            +
                        # turn the setting back to default so that form tags generate CSRF tags
         | 
| 38 | 
            +
                        rails_controller_instance.allow_forgery_protection = rails_controller.allow_forgery_protection
         | 
| 39 | 
            +
                        yield
         | 
| 40 | 
            +
                      end
         | 
| 41 | 
            +
                    end
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                    # Calls the controller to verify the authenticity token.
         | 
| 44 | 
            +
                    def rails_check_csrf!
         | 
| 45 | 
            +
                      rails_controller_instance.send(:verify_authenticity_token)
         | 
| 46 | 
            +
                    end
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                    # Hidden tag with Rails CSRF token inserted into Rodauth templates.
         | 
| 49 | 
            +
                    def rails_csrf_tag
         | 
| 50 | 
            +
                      %(<input type="hidden" name="#{rails_csrf_param}" value="#{rails_csrf_token}">)
         | 
| 51 | 
            +
                    end
         | 
| 52 | 
            +
             | 
| 53 | 
            +
                    # The request parameter under which to send the Rails CSRF token.
         | 
| 54 | 
            +
                    def rails_csrf_param
         | 
| 55 | 
            +
                      rails_controller.request_forgery_protection_token
         | 
| 56 | 
            +
                    end
         | 
| 57 | 
            +
             | 
| 58 | 
            +
                    # The Rails CSRF token value inserted into Rodauth templates.
         | 
| 59 | 
            +
                    def rails_csrf_token
         | 
| 60 | 
            +
                      rails_controller_instance.send(:form_authenticity_token)
         | 
| 61 | 
            +
                    end
         | 
| 62 | 
            +
                  end
         | 
| 63 | 
            +
                end
         | 
| 64 | 
            +
              end
         | 
| 65 | 
            +
            end
         | 
| @@ -0,0 +1,30 @@ | |
| 1 | 
            +
            module Rodauth
         | 
| 2 | 
            +
              module Rails
         | 
| 3 | 
            +
                module Feature
         | 
| 4 | 
            +
                  module Email
         | 
| 5 | 
            +
                    def self.included(feature)
         | 
| 6 | 
            +
                      feature.depends :email_base
         | 
| 7 | 
            +
                    end
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                    private
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                    # Create emails with ActionMailer which uses configured delivery method.
         | 
| 12 | 
            +
                    def create_email_to(to, subject, body)
         | 
| 13 | 
            +
                      Mailer.create_email(to: to, from: email_from, subject: "#{email_subject_prefix}#{subject}", body: body)
         | 
| 14 | 
            +
                    end
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                    # Delivers the given email.
         | 
| 17 | 
            +
                    def send_email(email)
         | 
| 18 | 
            +
                      email.deliver_now
         | 
| 19 | 
            +
                    end
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                    # ActionMailer subclass for correct email delivering.
         | 
| 22 | 
            +
                    class Mailer < ActionMailer::Base
         | 
| 23 | 
            +
                      def create_email(**options)
         | 
| 24 | 
            +
                        mail(**options)
         | 
| 25 | 
            +
                      end
         | 
| 26 | 
            +
                    end
         | 
| 27 | 
            +
                  end
         | 
| 28 | 
            +
                end
         | 
| 29 | 
            +
              end
         | 
| 30 | 
            +
            end
         | 
| @@ -0,0 +1,71 @@ | |
| 1 | 
            +
            module Rodauth
         | 
| 2 | 
            +
              module Rails
         | 
| 3 | 
            +
                module Feature
         | 
| 4 | 
            +
                  module Instrumentation
         | 
| 5 | 
            +
                    private
         | 
| 6 | 
            +
             | 
| 7 | 
            +
                    def _around_rodauth
         | 
| 8 | 
            +
                      rails_instrument_request { super }
         | 
| 9 | 
            +
                    end
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                    def redirect(*)
         | 
| 12 | 
            +
                      rails_instrument_redirection { super }
         | 
| 13 | 
            +
                    end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                    def rails_render(*)
         | 
| 16 | 
            +
                      render_output = nil
         | 
| 17 | 
            +
                      rails_controller_instance.view_runtime = rails_controller_instance.send(:cleanup_view_runtime) do
         | 
| 18 | 
            +
                        Benchmark.ms { render_output = super }
         | 
| 19 | 
            +
                      end
         | 
| 20 | 
            +
                      render_output
         | 
| 21 | 
            +
                    end
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                    def rails_instrument_request
         | 
| 24 | 
            +
                      request = rails_request
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                      raw_payload = {
         | 
| 27 | 
            +
                        controller: scope.class.superclass.name,
         | 
| 28 | 
            +
                        action: "call",
         | 
| 29 | 
            +
                        request: request,
         | 
| 30 | 
            +
                        params: request.filtered_parameters,
         | 
| 31 | 
            +
                        headers: request.headers,
         | 
| 32 | 
            +
                        format: request.format.ref,
         | 
| 33 | 
            +
                        method: request.request_method,
         | 
| 34 | 
            +
                        path: request.fullpath
         | 
| 35 | 
            +
                      }
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                      ActiveSupport::Notifications.instrument("start_processing.action_controller", raw_payload)
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                      ActiveSupport::Notifications.instrument("process_action.action_controller", raw_payload) do |payload|
         | 
| 40 | 
            +
                        begin
         | 
| 41 | 
            +
                          result = catch(:halt) { yield }
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                          response = ActionDispatch::Response.new *(result || [404, {}, []])
         | 
| 44 | 
            +
                          payload[:response] = response
         | 
| 45 | 
            +
                          payload[:status] = response.status
         | 
| 46 | 
            +
             | 
| 47 | 
            +
                          throw :halt, result if result
         | 
| 48 | 
            +
                        rescue => error
         | 
| 49 | 
            +
                          payload[:status] = ActionDispatch::ExceptionWrapper.status_code_for_exception(error.class.name)
         | 
| 50 | 
            +
                          raise
         | 
| 51 | 
            +
                        ensure
         | 
| 52 | 
            +
                          rails_controller_eval { append_info_to_payload(payload) }
         | 
| 53 | 
            +
                        end
         | 
| 54 | 
            +
                      end
         | 
| 55 | 
            +
                    end
         | 
| 56 | 
            +
             | 
| 57 | 
            +
                    def rails_instrument_redirection
         | 
| 58 | 
            +
                      ActiveSupport::Notifications.instrument("redirect_to.action_controller", request: rails_request) do |payload|
         | 
| 59 | 
            +
                        result = catch(:halt) { yield }
         | 
| 60 | 
            +
             | 
| 61 | 
            +
                        response = ActionDispatch::Response.new(*result)
         | 
| 62 | 
            +
                        payload[:status] = response.status
         | 
| 63 | 
            +
                        payload[:location] = response.filtered_location
         | 
| 64 | 
            +
             | 
| 65 | 
            +
                        throw :halt, result
         | 
| 66 | 
            +
                      end
         | 
| 67 | 
            +
                    end
         | 
| 68 | 
            +
                  end
         | 
| 69 | 
            +
                end
         | 
| 70 | 
            +
              end
         | 
| 71 | 
            +
            end
         | 
| @@ -0,0 +1,41 @@ | |
| 1 | 
            +
            module Rodauth
         | 
| 2 | 
            +
              module Rails
         | 
| 3 | 
            +
                module Feature
         | 
| 4 | 
            +
                  module Render
         | 
| 5 | 
            +
                    def self.included(feature)
         | 
| 6 | 
            +
                      feature.auth_methods :rails_render
         | 
| 7 | 
            +
                    end
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                    # Renders templates with layout. First tries to render a user-defined
         | 
| 10 | 
            +
                    # template, otherwise falls back to Rodauth's template.
         | 
| 11 | 
            +
                    def view(page, *)
         | 
| 12 | 
            +
                      rails_render(action: page.tr("-", "_"), layout: true) ||
         | 
| 13 | 
            +
                        rails_render(html: super.html_safe, layout: true)
         | 
| 14 | 
            +
                    end
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                    # Renders templates without layout. First tries to render a user-defined
         | 
| 17 | 
            +
                    # template or partial, otherwise falls back to Rodauth's template.
         | 
| 18 | 
            +
                    def render(page)
         | 
| 19 | 
            +
                      rails_render(partial: page.tr("-", "_"), layout: false) ||
         | 
| 20 | 
            +
                        rails_render(action: page.tr("-", "_"), layout: false) ||
         | 
| 21 | 
            +
                        super.html_safe
         | 
| 22 | 
            +
                    end
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                    def button(*)
         | 
| 25 | 
            +
                      super.html_safe
         | 
| 26 | 
            +
                    end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                    private
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                    # Calls the Rails renderer, returning nil if a template is missing.
         | 
| 31 | 
            +
                    def rails_render(*args)
         | 
| 32 | 
            +
                      return if rails_api_controller?
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                      rails_controller_instance.render_to_string(*args)
         | 
| 35 | 
            +
                    rescue ActionView::MissingTemplate
         | 
| 36 | 
            +
                      nil
         | 
| 37 | 
            +
                    end
         | 
| 38 | 
            +
                  end
         | 
| 39 | 
            +
                end
         | 
| 40 | 
            +
              end
         | 
| 41 | 
            +
            end
         | 
| @@ -1,6 +1,5 @@ | |
| 1 1 | 
             
            require "rodauth/rails/middleware"
         | 
| 2 2 | 
             
            require "rodauth/rails/controller_methods"
         | 
| 3 | 
            -
            require "rodauth/rails/log_subscriber"
         | 
| 4 3 |  | 
| 5 4 | 
             
            require "rails"
         | 
| 6 5 |  | 
| @@ -17,10 +16,6 @@ module Rodauth | |
| 17 16 | 
             
                    end
         | 
| 18 17 | 
             
                  end
         | 
| 19 18 |  | 
| 20 | 
            -
                  initializer "rodauth.log_subscriber" do
         | 
| 21 | 
            -
                    Rodauth::Rails::LogSubscriber.attach_to :rodauth
         | 
| 22 | 
            -
                  end
         | 
| 23 | 
            -
             | 
| 24 19 | 
             
                  initializer "rodauth.test" do
         | 
| 25 20 | 
             
                    # Rodauth uses RACK_ENV to set the default bcrypt hash cost
         | 
| 26 21 | 
             
                    ENV["RACK_ENV"] = "test" if ::Rails.env.test?
         | 
    
        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.12.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-05- | 
| 11 | 
            +
            date: 2021-05-15 00:00:00.000000000 Z
         | 
| 12 12 | 
             
            dependencies:
         | 
| 13 13 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 14 14 | 
             
              name: railties
         | 
| @@ -207,7 +207,12 @@ files: | |
| 207 207 | 
             
            - lib/rodauth/rails/auth.rb
         | 
| 208 208 | 
             
            - lib/rodauth/rails/controller_methods.rb
         | 
| 209 209 | 
             
            - lib/rodauth/rails/feature.rb
         | 
| 210 | 
            -
            - lib/rodauth/rails/ | 
| 210 | 
            +
            - lib/rodauth/rails/feature/base.rb
         | 
| 211 | 
            +
            - lib/rodauth/rails/feature/callbacks.rb
         | 
| 212 | 
            +
            - lib/rodauth/rails/feature/csrf.rb
         | 
| 213 | 
            +
            - lib/rodauth/rails/feature/email.rb
         | 
| 214 | 
            +
            - lib/rodauth/rails/feature/instrumentation.rb
         | 
| 215 | 
            +
            - lib/rodauth/rails/feature/render.rb
         | 
| 211 216 | 
             
            - lib/rodauth/rails/middleware.rb
         | 
| 212 217 | 
             
            - lib/rodauth/rails/railtie.rb
         | 
| 213 218 | 
             
            - lib/rodauth/rails/tasks.rake
         | 
| @@ -1,34 +0,0 @@ | |
| 1 | 
            -
            module Rodauth
         | 
| 2 | 
            -
              module Rails
         | 
| 3 | 
            -
                class LogSubscriber < ActiveSupport::LogSubscriber
         | 
| 4 | 
            -
                  def start_processing(event)
         | 
| 5 | 
            -
                    rodauth = event.payload[:rodauth]
         | 
| 6 | 
            -
                    app_class = rodauth.scope.class.superclass
         | 
| 7 | 
            -
                    format = rodauth.rails_request.format.ref
         | 
| 8 | 
            -
                    format = format.to_s.upcase if format.is_a?(Symbol)
         | 
| 9 | 
            -
                    format = "*/*" if format.nil?
         | 
| 10 | 
            -
             | 
| 11 | 
            -
                    info "Processing by #{app_class} as #{format}"
         | 
| 12 | 
            -
                  end
         | 
| 13 | 
            -
             | 
| 14 | 
            -
                  def process_request(event)
         | 
| 15 | 
            -
                    status = event.payload[:status]
         | 
| 16 | 
            -
             | 
| 17 | 
            -
                    additions = ActionController::Base.log_process_action(event.payload)
         | 
| 18 | 
            -
                    if ::Rails.gem_version >= Gem::Version.new("6.0")
         | 
| 19 | 
            -
                      additions << "Allocations: #{event.allocations}"
         | 
| 20 | 
            -
                    end
         | 
| 21 | 
            -
             | 
| 22 | 
            -
                    message = "Completed #{status} #{Rack::Utils::HTTP_STATUS_CODES[status]} in #{event.duration.round}ms"
         | 
| 23 | 
            -
                    message << " (#{additions.join(" | ")})"
         | 
| 24 | 
            -
                    message << "\n\n" if defined?(::Rails.env) && ::Rails.env.development?
         | 
| 25 | 
            -
             | 
| 26 | 
            -
                    info message
         | 
| 27 | 
            -
                  end
         | 
| 28 | 
            -
             | 
| 29 | 
            -
                  def logger
         | 
| 30 | 
            -
                    ::Rails.logger
         | 
| 31 | 
            -
                  end
         | 
| 32 | 
            -
                end
         | 
| 33 | 
            -
              end
         | 
| 34 | 
            -
            end
         |