rodauth-rails 0.6.1 → 0.9.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 +38 -0
- data/README.md +369 -47
- data/lib/generators/rodauth/install_generator.rb +9 -4
- data/lib/generators/rodauth/templates/app/lib/rodauth_app.rb +23 -24
- data/lib/rodauth/rails.rb +20 -0
- data/lib/rodauth/rails/app.rb +9 -7
- data/lib/rodauth/rails/app/flash.rb +5 -3
- data/lib/rodauth/rails/feature.rb +40 -15
- data/lib/rodauth/rails/tasks.rake +1 -1
- data/lib/rodauth/rails/version.rb +1 -1
- data/rodauth-rails.gemspec +3 -1
- metadata +22 -9
- data/lib/rodauth/features/rails.rb +0 -1
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: 60fda35b195285a7c9cc14e07153d80faa9e939cf2944fbb04e1360baf30e306
         | 
| 4 | 
            +
              data.tar.gz: 1f89bcfff28e6d08287fa67a9fe9228a2d61f15a8a9cdecb9fcf138137d72c47
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: 8e4ed3afbe7a114ba36f19541d1c8c8ee62de07526400230b1386f027b05876cd78ad88bdb40cae9767e74f83d1532d4939d97d03657662933f81d7086df34d9
         | 
| 7 | 
            +
              data.tar.gz: cae1fc15a86f1b2e2423a8e54f36b844f610ba23ff74fd9ced132200e9816260028682c0efea1dcf19cf8a722a7ae49882c8d31c670e268e229117b4f6fb84f2
         | 
    
        data/CHANGELOG.md
    CHANGED
    
    | @@ -1,3 +1,41 @@ | |
| 1 | 
            +
            ## 0.9.0 (2021-02-07)
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            * Load Roda's JSON support by default, so that enabling `json`/`jwt` feature is all that's needed (@janko)
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            * Bump Rodauth dependency to 2.9+ (@janko)
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            * Add `--json` option for `rodauth:install` generator for configuring `json` feature (@janko)
         | 
| 8 | 
            +
             | 
| 9 | 
            +
            * Add `--jwt` option for `rodauth:install` generator for configuring `jwt` feature (@janko)
         | 
| 10 | 
            +
             | 
| 11 | 
            +
            * Remove the `--api` option from `rodauth:install` generator (@janko)
         | 
| 12 | 
            +
             | 
| 13 | 
            +
            ## 0.8.2 (2021-01-10)
         | 
| 14 | 
            +
             | 
| 15 | 
            +
            * Reset Rails session on `#clear_session`, protecting from potential session fixation attacks (@janko)
         | 
| 16 | 
            +
             | 
| 17 | 
            +
            ## 0.8.1 (2021-01-04)
         | 
| 18 | 
            +
             | 
| 19 | 
            +
            * Fix blank email body when `json: true` and `ActionController::API` descendant are used (@janko)
         | 
| 20 | 
            +
             | 
| 21 | 
            +
            * Make view and email rendering work when there are multiple configurations and one is `json: :only` (@janko)
         | 
| 22 | 
            +
             | 
| 23 | 
            +
            * Don't attempt to protect against forgery when `ActionController::API` descendant is used (@janko)
         | 
| 24 | 
            +
             | 
| 25 | 
            +
            * Mark content of rodauth built-in partials as HTML-safe (@janko)
         | 
| 26 | 
            +
             | 
| 27 | 
            +
            ## 0.8.0 (2021-01-03)
         | 
| 28 | 
            +
             | 
| 29 | 
            +
            * Add `--api` option to `rodauth:install` generator for choosing JSON-only configuration (@janko)
         | 
| 30 | 
            +
             | 
| 31 | 
            +
            * Don't blow up when a Rodauth request is made using an unsupported HTTP verb (@janko)
         | 
| 32 | 
            +
             | 
| 33 | 
            +
            ## 0.7.0 (2020-11-27)
         | 
| 34 | 
            +
             | 
| 35 | 
            +
            * Add `#rails_controller_eval` method for running code in context of a controller instance (@janko)
         | 
| 36 | 
            +
             | 
| 37 | 
            +
            * Detect `secret_key_base` from credentials and `$SECRET_KEY_BASE` environment variable (@janko)
         | 
| 38 | 
            +
             | 
| 1 39 | 
             
            ## 0.6.1 (2020-11-25)
         | 
| 2 40 |  | 
| 3 41 | 
             
            * Generate the Rodauth controller for API-only Rails apps as well (@janko)
         | 
    
        data/README.md
    CHANGED
    
    | @@ -2,6 +2,35 @@ | |
| 2 2 |  | 
| 3 3 | 
             
            Provides Rails integration for the [Rodauth] authentication framework.
         | 
| 4 4 |  | 
| 5 | 
            +
            ## Table of contents
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            * [Resources](#resources)
         | 
| 8 | 
            +
            * [Why Rodauth?](#why-rodauth)
         | 
| 9 | 
            +
            * [Upgrading](#upgrading)
         | 
| 10 | 
            +
            * [Installation](#installation)
         | 
| 11 | 
            +
            * [Usage](#usage)
         | 
| 12 | 
            +
              - [Routes](#routes)
         | 
| 13 | 
            +
              - [Current account](#current-account)
         | 
| 14 | 
            +
              - [Requiring authentication](#requiring-authentication)
         | 
| 15 | 
            +
              - [Views](#views)
         | 
| 16 | 
            +
              - [Mailer](#mailer)
         | 
| 17 | 
            +
              - [Migrations](#migrations)
         | 
| 18 | 
            +
              - [Multiple configurations](#multiple-configurations)
         | 
| 19 | 
            +
              - [Calling controller methods](#calling-controller-methods)
         | 
| 20 | 
            +
              - [Rodauth instance](#rodauth-instance)
         | 
| 21 | 
            +
            * [How it works](#how-it-works)
         | 
| 22 | 
            +
              - [Middleware](#middleware)
         | 
| 23 | 
            +
              - [App](#app)
         | 
| 24 | 
            +
              - [Sequel](#sequel)
         | 
| 25 | 
            +
            * [JSON API](#json-api)
         | 
| 26 | 
            +
            * [OmniAuth](#omniauth)
         | 
| 27 | 
            +
            * [Configuring](#configuring)
         | 
| 28 | 
            +
            * [Custom extensions](#custom-extensions)
         | 
| 29 | 
            +
            * [Testing](#testing)
         | 
| 30 | 
            +
            * [Rodauth defaults](#rodauth-defaults)
         | 
| 31 | 
            +
              - [Database functions](#database-functions)
         | 
| 32 | 
            +
              - [Account statuses](#account-statuses)
         | 
| 33 | 
            +
             | 
| 5 34 | 
             
            ## Resources
         | 
| 6 35 |  | 
| 7 36 | 
             
            Useful links:
         | 
| @@ -12,14 +41,49 @@ Useful links: | |
| 12 41 | 
             
            Articles:
         | 
| 13 42 |  | 
| 14 43 | 
             
            * [Rodauth: A Refreshing Authentication Solution for Ruby](https://janko.io/rodauth-a-refreshing-authentication-solution-for-ruby/)
         | 
| 15 | 
            -
            * [Adding Authentication in Rails  | 
| 44 | 
            +
            * [Adding Authentication in Rails with Rodauth](https://janko.io/adding-authentication-in-rails-with-rodauth/)
         | 
| 45 | 
            +
            * [Adding Multifactor Authentication in Rails with Rodauth](https://janko.io/adding-multifactor-authentication-in-rails-with-rodauth/)
         | 
| 46 | 
            +
             | 
| 47 | 
            +
            ## Why Rodauth?
         | 
| 48 | 
            +
             | 
| 49 | 
            +
            There are already several popular authentication solutions for Rails (Devise,
         | 
| 50 | 
            +
            Sorcery, Clearance, Authlogic), so why would you choose Rodauth? Here are some
         | 
| 51 | 
            +
            of the advantages that stand out for me:
         | 
| 52 | 
            +
             | 
| 53 | 
            +
            * multifactor authentication ([TOTP][otp], [SMS codes][sms_codes], [recovery codes][recovery_codes], [WebAuthn][webauthn])
         | 
| 54 | 
            +
            * standardized [JSON API support][json] for every feature (including [JWT][jwt])
         | 
| 55 | 
            +
            * enterprise security features ([password complexity][password_complexity], [disallow password reuse][disallow_password_reuse], [password expiration][password_expiration], [session expiration][session_expiration], [single session][single_session], [account expiration][account_expiration])
         | 
| 56 | 
            +
            * [email authentication][email_auth] (aka "passwordless")
         | 
| 57 | 
            +
            * [audit logging][audit_logging] (for any action)
         | 
| 58 | 
            +
            * ability to protect password hashes even in case of SQL injection ([more details][password protection])
         | 
| 59 | 
            +
            * additional bruteforce protection for tokens ([more details][bruteforce tokens])
         | 
| 60 | 
            +
            * uniform configuration DSL (any setting can be static or dynamic)
         | 
| 61 | 
            +
            * consistent before/after hooks around everything
         | 
| 62 | 
            +
            * dedicated object encapsulating all authentication logic
         | 
| 63 | 
            +
             | 
| 64 | 
            +
            ## Upgrading
         | 
| 65 | 
            +
             | 
| 66 | 
            +
            ### Upgrading to 0.7.0
         | 
| 67 | 
            +
             | 
| 68 | 
            +
            Starting from version 0.7.0, rodauth-rails now correctly detects Rails
         | 
| 69 | 
            +
            application's `secret_key_base` when setting default `hmac_secret`, including
         | 
| 70 | 
            +
            when it's set via credentials or `$SECRET_KEY_BASE` environment variable. This
         | 
| 71 | 
            +
            means that your authentication will now be more secure by default, and Rodauth
         | 
| 72 | 
            +
            features that require `hmac_secret` should now work automatically as well.
         | 
| 73 | 
            +
             | 
| 74 | 
            +
            However, if you've already been using rodauth-rails in production, where the
         | 
| 75 | 
            +
            `secret_key_base` is set via credentials or environment variable and `hmac_secret`
         | 
| 76 | 
            +
            was not explicitly set, the fact that your authentication will now start using
         | 
| 77 | 
            +
            HMACs has backwards compatibility considerations. See the [Rodauth
         | 
| 78 | 
            +
            documentation][hmac] for instructions on how to safely transition, or just set
         | 
| 79 | 
            +
            `hmac_secret nil` in your Rodauth configuration.
         | 
| 16 80 |  | 
| 17 81 | 
             
            ## Installation
         | 
| 18 82 |  | 
| 19 83 | 
             
            Add the gem to your Gemfile:
         | 
| 20 84 |  | 
| 21 85 | 
             
            ```rb
         | 
| 22 | 
            -
            gem "rodauth-rails", "~> 0. | 
| 86 | 
            +
            gem "rodauth-rails", "~> 0.9"
         | 
| 23 87 |  | 
| 24 88 | 
             
            # gem "jwt",      require: false # for JWT feature
         | 
| 25 89 | 
             
            # gem "rotp",     require: false # for OTP feature
         | 
| @@ -31,10 +95,19 @@ Then run `bundle install`. | |
| 31 95 |  | 
| 32 96 | 
             
            Next, run the install generator:
         | 
| 33 97 |  | 
| 34 | 
            -
            ```
         | 
| 98 | 
            +
            ```sh
         | 
| 35 99 | 
             
            $ rails generate rodauth:install
         | 
| 36 100 | 
             
            ```
         | 
| 37 101 |  | 
| 102 | 
            +
            Or if you want Rodauth endpoints to be exposed via JSON API:
         | 
| 103 | 
            +
             | 
| 104 | 
            +
            ```sh
         | 
| 105 | 
            +
            $ rails generate rodauth:install --json # regular authentication using the Rails session
         | 
| 106 | 
            +
            # or
         | 
| 107 | 
            +
            $ rails generate rodauth:install --jwt # token authentication via the "Authorization" header
         | 
| 108 | 
            +
            $ bundle add jwt
         | 
| 109 | 
            +
            ```
         | 
| 110 | 
            +
             | 
| 38 111 | 
             
            The generator will create the following files:
         | 
| 39 112 |  | 
| 40 113 | 
             
            * Rodauth migration at `db/migrate/*_create_rodauth.rb`
         | 
| @@ -168,14 +241,12 @@ Using this information, we could add some basic authentication links to our | |
| 168 241 | 
             
            navigation header:
         | 
| 169 242 |  | 
| 170 243 | 
             
            ```erb
         | 
| 171 | 
            -
             | 
| 172 | 
            -
               | 
| 173 | 
            -
             | 
| 174 | 
            -
               | 
| 175 | 
            -
             | 
| 176 | 
            -
             | 
| 177 | 
            -
              <% end %>
         | 
| 178 | 
            -
            </ul>
         | 
| 244 | 
            +
            <% if rodauth.logged_in? %>
         | 
| 245 | 
            +
              <%= link_to "Sign out", rodauth.logout_path, method: :post %>
         | 
| 246 | 
            +
            <% else %>
         | 
| 247 | 
            +
              <%= link_to "Sign in", rodauth.login_path %>
         | 
| 248 | 
            +
              <%= link_to "Sign up", rodauth.create_account_path %>
         | 
| 249 | 
            +
            <% end %>
         | 
| 179 250 | 
             
            ```
         | 
| 180 251 |  | 
| 181 252 | 
             
            These routes are fully functional, feel free to visit them and interact with the
         | 
| @@ -191,7 +262,7 @@ retrieves the corresponding account record: | |
| 191 262 | 
             
            ```rb
         | 
| 192 263 | 
             
            # app/controllers/application_controller.rb
         | 
| 193 264 | 
             
            class ApplicationController < ActionController::Base
         | 
| 194 | 
            -
              before_action :current_account, if: -> { rodauth. | 
| 265 | 
            +
              before_action :current_account, if: -> { rodauth.logged_in? }
         | 
| 195 266 |  | 
| 196 267 | 
             
              private
         | 
| 197 268 |  | 
| @@ -365,7 +436,7 @@ $ rails generate rodauth:mailer | |
| 365 436 | 
             
            ```
         | 
| 366 437 |  | 
| 367 438 | 
             
            This will create a `RodauthMailer` with the associated mailer views in
         | 
| 368 | 
            -
            `app/views/rodauth_mailer` directory | 
| 439 | 
            +
            `app/views/rodauth_mailer` directory:
         | 
| 369 440 |  | 
| 370 441 | 
             
            ```rb
         | 
| 371 442 | 
             
            # app/mailers/rodauth_mailer.rb
         | 
| @@ -417,9 +488,9 @@ end | |
| 417 488 | 
             
            ```
         | 
| 418 489 |  | 
| 419 490 | 
             
            This approach can be used even if you're using a 3rd-party service for
         | 
| 420 | 
            -
            transactional emails, where emails are sent via  | 
| 421 | 
            -
             | 
| 422 | 
            -
             | 
| 491 | 
            +
            transactional emails, where emails are sent via HTTP instead of SMTP. Whatever
         | 
| 492 | 
            +
            the `create_*_email` block returns will be passed to `send_email`, so you can
         | 
| 493 | 
            +
            be creative.
         | 
| 423 494 |  | 
| 424 495 | 
             
            ### Migrations
         | 
| 425 496 |  | 
| @@ -441,36 +512,67 @@ class CreateRodauthOtpSmsCodesRecoveryCodes < ActiveRecord::Migration | |
| 441 512 | 
             
            end
         | 
| 442 513 | 
             
            ```
         | 
| 443 514 |  | 
| 444 | 
            -
            ###  | 
| 445 | 
            -
             | 
| 446 | 
            -
            JSON API support in Rodauth is provided by the [JWT feature]. First you'll need
         | 
| 447 | 
            -
            to add the [JWT gem] to your Gemfile:
         | 
| 448 | 
            -
             | 
| 449 | 
            -
            ```rb
         | 
| 450 | 
            -
            gem "jwt"
         | 
| 451 | 
            -
            ```
         | 
| 515 | 
            +
            ### Multiple configurations
         | 
| 452 516 |  | 
| 453 | 
            -
             | 
| 454 | 
            -
             | 
| 517 | 
            +
            If you need to handle multiple types of accounts that require different
         | 
| 518 | 
            +
            authentication logic, you can create different configurations for them:
         | 
| 455 519 |  | 
| 456 520 | 
             
            ```rb
         | 
| 457 521 | 
             
            # app/lib/rodauth_app.rb
         | 
| 458 522 | 
             
            class RodauthApp < Rodauth::Rails::App
         | 
| 459 | 
            -
               | 
| 523 | 
            +
              # primary configuration
         | 
| 524 | 
            +
              configure do
         | 
| 460 525 | 
             
                # ...
         | 
| 461 | 
            -
             | 
| 462 | 
            -
             | 
| 526 | 
            +
              end
         | 
| 527 | 
            +
             | 
| 528 | 
            +
              # alternative configuration
         | 
| 529 | 
            +
              configure(:admin) do
         | 
| 530 | 
            +
                # ... enable features ...
         | 
| 531 | 
            +
                prefix "/admin"
         | 
| 532 | 
            +
                session_key_prefix "admin_"
         | 
| 533 | 
            +
                remember_cookie_key "_admin_remember" # if using remember feature
         | 
| 534 | 
            +
                # ...
         | 
| 535 | 
            +
              end
         | 
| 536 | 
            +
             | 
| 537 | 
            +
              route do |r|
         | 
| 538 | 
            +
                r.rodauth
         | 
| 539 | 
            +
                r.on("admin") { r.rodauth(:admin) }
         | 
| 463 540 | 
             
                # ...
         | 
| 464 541 | 
             
              end
         | 
| 465 542 | 
             
            end
         | 
| 466 543 | 
             
            ```
         | 
| 467 544 |  | 
| 468 | 
            -
             | 
| 469 | 
            -
            Rails app is in API-only mode, instead of `json: true` pass `json: :only` to
         | 
| 470 | 
            -
            the configure method.
         | 
| 545 | 
            +
            Then in your application you can reference the secondary Rodauth instance:
         | 
| 471 546 |  | 
| 472 | 
            -
             | 
| 473 | 
            -
             | 
| 547 | 
            +
            ```rb
         | 
| 548 | 
            +
            rodauth(:admin).login_path #=> "/admin/login"
         | 
| 549 | 
            +
            ```
         | 
| 550 | 
            +
             | 
| 551 | 
            +
            ### Calling controller methods
         | 
| 552 | 
            +
             | 
| 553 | 
            +
            When using Rodauth before/after hooks or generally overriding your Rodauth
         | 
| 554 | 
            +
            configuration, in some cases you might want to call methods defined on your
         | 
| 555 | 
            +
            controllers. You can do so with `rails_controller_eval`, for example:
         | 
| 556 | 
            +
             | 
| 557 | 
            +
            ```rb
         | 
| 558 | 
            +
            # app/controllers/application_controller.rb
         | 
| 559 | 
            +
            class ApplicationController < ActionController::Base
         | 
| 560 | 
            +
              private
         | 
| 561 | 
            +
              def setup_tracking(account_id)
         | 
| 562 | 
            +
                # ... some implementation ...
         | 
| 563 | 
            +
              end
         | 
| 564 | 
            +
            end
         | 
| 565 | 
            +
            ```
         | 
| 566 | 
            +
            ```rb
         | 
| 567 | 
            +
            # app/lib/rodauth_app.rb
         | 
| 568 | 
            +
            class RodauthApp < Rodauth::Rails::App
         | 
| 569 | 
            +
              configure do
         | 
| 570 | 
            +
                after_create_account do
         | 
| 571 | 
            +
                  rails_controller_eval { setup_tracking(account_id) }
         | 
| 572 | 
            +
                end
         | 
| 573 | 
            +
              end
         | 
| 574 | 
            +
            end
         | 
| 575 | 
            +
            ```
         | 
| 474 576 |  | 
| 475 577 | 
             
            ### Rodauth instance
         | 
| 476 578 |  | 
| @@ -479,7 +581,7 @@ Rodauth operations outside of the request context. rodauth-rails gives you the | |
| 479 581 | 
             
            ability to retrieve the Rodauth instance:
         | 
| 480 582 |  | 
| 481 583 | 
             
            ```rb
         | 
| 482 | 
            -
            rodauth = Rodauth::Rails.rodauth # or Rodauth::Rails.rodauth(: | 
| 584 | 
            +
            rodauth = Rodauth::Rails.rodauth # or Rodauth::Rails.rodauth(:admin)
         | 
| 483 585 |  | 
| 484 586 | 
             
            rodauth.login_url #=> "https://example.com/login"
         | 
| 485 587 | 
             
            rodauth.account_from_login("user@example.com") # loads user by email
         | 
| @@ -510,8 +612,8 @@ The Rodauth app stores the `Rodauth::Auth` instance in the Rack env hash, which | |
| 510 612 | 
             
            is then available in your Rails app:
         | 
| 511 613 |  | 
| 512 614 | 
             
            ```rb
         | 
| 513 | 
            -
            request.env["rodauth"] | 
| 514 | 
            -
            request.env["rodauth. | 
| 615 | 
            +
            request.env["rodauth"]       #=> #<Rodauth::Auth>
         | 
| 616 | 
            +
            request.env["rodauth.admin"] #=> #<Rodauth::Auth> (if using multiple configurations)
         | 
| 515 617 | 
             
            ```
         | 
| 516 618 |  | 
| 517 619 | 
             
            For convenience, this object can be accessed via the `#rodauth` method in views
         | 
| @@ -520,14 +622,14 @@ and controllers: | |
| 520 622 | 
             
            ```rb
         | 
| 521 623 | 
             
            class MyController < ApplicationController
         | 
| 522 624 | 
             
              def my_action
         | 
| 523 | 
            -
                rodauth | 
| 524 | 
            -
                rodauth(: | 
| 625 | 
            +
                rodauth         #=> #<Rodauth::Auth>
         | 
| 626 | 
            +
                rodauth(:admin) #=> #<Rodauth::Auth> (if using multiple configurations)
         | 
| 525 627 | 
             
              end
         | 
| 526 628 | 
             
            end
         | 
| 527 629 | 
             
            ```
         | 
| 528 630 | 
             
            ```erb
         | 
| 529 | 
            -
            <% rodauth | 
| 530 | 
            -
            <% rodauth(: | 
| 631 | 
            +
            <% rodauth         #=> #<Rodauth::Auth> %>
         | 
| 632 | 
            +
            <% rodauth(:admin) #=> #<Rodauth::Auth> (if using multiple configurations) %>
         | 
| 531 633 | 
             
            ```
         | 
| 532 634 |  | 
| 533 635 | 
             
            ### App
         | 
| @@ -542,13 +644,38 @@ integration for Rodauth: | |
| 542 644 | 
             
            * runs Action Controller callbacks & rescue handlers around Rodauth actions
         | 
| 543 645 | 
             
            * uses Action Mailer for sending emails
         | 
| 544 646 |  | 
| 545 | 
            -
            The `configure | 
| 647 | 
            +
            The `configure` method wraps configuring the Rodauth plugin, forwarding
         | 
| 546 648 | 
             
            any additional [plugin options].
         | 
| 547 649 |  | 
| 548 650 | 
             
            ```rb
         | 
| 549 | 
            -
             | 
| 550 | 
            -
            configure | 
| 551 | 
            -
            configure(: | 
| 651 | 
            +
            class RodauthApp < Rodauth::Rails::App
         | 
| 652 | 
            +
              configure { ... }             # defining default Rodauth configuration
         | 
| 653 | 
            +
              configure(json: true) { ... } # passing options to the Rodauth plugin
         | 
| 654 | 
            +
              configure(:admin) { ... }     # defining multiple Rodauth configurations
         | 
| 655 | 
            +
            end
         | 
| 656 | 
            +
            ```
         | 
| 657 | 
            +
             | 
| 658 | 
            +
            The `route` block is provided by Roda, and it's called on each request before
         | 
| 659 | 
            +
            it reaches the Rails router.
         | 
| 660 | 
            +
             | 
| 661 | 
            +
            ```rb
         | 
| 662 | 
            +
            class RodauthApp < Rodauth::Rails::App
         | 
| 663 | 
            +
              route do |r|
         | 
| 664 | 
            +
                # ... called before each request ...
         | 
| 665 | 
            +
              end
         | 
| 666 | 
            +
            end
         | 
| 667 | 
            +
            ```
         | 
| 668 | 
            +
             | 
| 669 | 
            +
            Since `Rodauth::Rails::App` is just a Roda subclass, you can do anything you
         | 
| 670 | 
            +
            would with a Roda app, such as loading additional Roda plugins:
         | 
| 671 | 
            +
             | 
| 672 | 
            +
            ```rb
         | 
| 673 | 
            +
            class RodauthApp < Rodauth::Rails::App
         | 
| 674 | 
            +
              plugin :request_headers # easier access to request headers
         | 
| 675 | 
            +
              plugin :typecast_params # methods for conversion of request params
         | 
| 676 | 
            +
              plugin :default_headers, { "Foo" => "Bar" }
         | 
| 677 | 
            +
              # ...
         | 
| 678 | 
            +
            end
         | 
| 552 679 | 
             
            ```
         | 
| 553 680 |  | 
| 554 681 | 
             
            ### Sequel
         | 
| @@ -559,11 +686,156 @@ function calls). | |
| 559 686 |  | 
| 560 687 | 
             
            If ActiveRecord is used in the application, the `rodauth:install` generator
         | 
| 561 688 | 
             
            will have automatically configured Sequel to reuse ActiveRecord's database
         | 
| 562 | 
            -
            connection  | 
| 689 | 
            +
            connection, using the [sequel-activerecord_connection] gem.
         | 
| 563 690 |  | 
| 564 691 | 
             
            This means that, from the usage perspective, Sequel can be considered just
         | 
| 565 692 | 
             
            as an implementation detail of Rodauth.
         | 
| 566 693 |  | 
| 694 | 
            +
            ## JSON API
         | 
| 695 | 
            +
             | 
| 696 | 
            +
            To make Rodauth endpoints accessible via JSON API, enable the [`json`][json]
         | 
| 697 | 
            +
            feature:
         | 
| 698 | 
            +
             | 
| 699 | 
            +
            ```rb
         | 
| 700 | 
            +
            # app/lib/rodauth_app.rb
         | 
| 701 | 
            +
            class RodauthApp < Rodauth::Rails::App
         | 
| 702 | 
            +
              configure do
         | 
| 703 | 
            +
                # ...
         | 
| 704 | 
            +
                enable :json
         | 
| 705 | 
            +
                only_json? true # accept only JSON requests
         | 
| 706 | 
            +
                # ...
         | 
| 707 | 
            +
              end
         | 
| 708 | 
            +
            end
         | 
| 709 | 
            +
            ```
         | 
| 710 | 
            +
             | 
| 711 | 
            +
            This will store account session data into the Rails session. If you rather want
         | 
| 712 | 
            +
            stateless token-based authentication via the `Authorization` header, enable the
         | 
| 713 | 
            +
            [`jwt`][jwt] feature (which builds on top of the `json` feature) and add the
         | 
| 714 | 
            +
            [JWT gem] to the Gemfile:
         | 
| 715 | 
            +
             | 
| 716 | 
            +
            ```sh
         | 
| 717 | 
            +
            $ bundle add jwt
         | 
| 718 | 
            +
            ```
         | 
| 719 | 
            +
            ```rb
         | 
| 720 | 
            +
            # app/lib/rodauth_app.rb
         | 
| 721 | 
            +
            class RodauthApp < Rodauth::Rails::App
         | 
| 722 | 
            +
              configure do
         | 
| 723 | 
            +
                # ...
         | 
| 724 | 
            +
                enable :jwt
         | 
| 725 | 
            +
                jwt_secret "<YOUR_SECRET_KEY>" # store the JWT secret in a safe place
         | 
| 726 | 
            +
                only_json? true # accept only JSON requests
         | 
| 727 | 
            +
                # ...
         | 
| 728 | 
            +
              end
         | 
| 729 | 
            +
            end
         | 
| 730 | 
            +
            ```
         | 
| 731 | 
            +
             | 
| 732 | 
            +
            ## OmniAuth
         | 
| 733 | 
            +
             | 
| 734 | 
            +
            While Rodauth doesn't yet come with [OmniAuth] integration, we can build one
         | 
| 735 | 
            +
            ourselves using the existing Rodauth API.
         | 
| 736 | 
            +
             | 
| 737 | 
            +
            In order to allow the user to login via multiple external providers, let's
         | 
| 738 | 
            +
            create an `account_identities` table that will have a many-to-one relationship
         | 
| 739 | 
            +
            with the `accounts` table:
         | 
| 740 | 
            +
             | 
| 741 | 
            +
            ```sh
         | 
| 742 | 
            +
            $ rails generate model AccountIdentity
         | 
| 743 | 
            +
            ```
         | 
| 744 | 
            +
            ```rb
         | 
| 745 | 
            +
            # db/migrate/*_create_account_identities.rb
         | 
| 746 | 
            +
            class CreateAccountIdentities < ActiveRecord::Migration
         | 
| 747 | 
            +
              def change
         | 
| 748 | 
            +
                create_table :account_identities do |t|
         | 
| 749 | 
            +
                  t.references :account, null: false, foreign_key: { on_delete: :cascade }
         | 
| 750 | 
            +
                  t.string :provider, null: false
         | 
| 751 | 
            +
                  t.string :uid, null: false
         | 
| 752 | 
            +
                  t.jsonb :info, null: false, default: {} # adjust JSON column type for your database
         | 
| 753 | 
            +
             | 
| 754 | 
            +
                  t.timestamps
         | 
| 755 | 
            +
             | 
| 756 | 
            +
                  t.index [:provider, :uid], unique: true
         | 
| 757 | 
            +
                end
         | 
| 758 | 
            +
              end
         | 
| 759 | 
            +
            end
         | 
| 760 | 
            +
            ```
         | 
| 761 | 
            +
            ```rb
         | 
| 762 | 
            +
            # app/models/account_identity.rb
         | 
| 763 | 
            +
            class AcccountIdentity < ApplicationRecord
         | 
| 764 | 
            +
              belongs_to :account
         | 
| 765 | 
            +
            end
         | 
| 766 | 
            +
            ```
         | 
| 767 | 
            +
            ```rb
         | 
| 768 | 
            +
            # app/models/account.rb
         | 
| 769 | 
            +
            class Account < ApplicationRecord
         | 
| 770 | 
            +
              has_many :identities, class_name: "AccountIdentity"
         | 
| 771 | 
            +
            end
         | 
| 772 | 
            +
            ```
         | 
| 773 | 
            +
             | 
| 774 | 
            +
            Let's assume we want to implement Facebook login, and have added the
         | 
| 775 | 
            +
            corresponding OmniAuth strategy to the middleware stack, together with an
         | 
| 776 | 
            +
            authorization link on the login form:
         | 
| 777 | 
            +
             | 
| 778 | 
            +
            ```rb
         | 
| 779 | 
            +
            Rails.application.config.middleware.use OmniAuth::Builder do
         | 
| 780 | 
            +
              provider :facebook, ENV["FACEBOOK_APP_ID"], ENV["FACEBOOK_APP_SECRET"],
         | 
| 781 | 
            +
                scope: "email", callback_path: "/auth/facebook/callback"
         | 
| 782 | 
            +
            end
         | 
| 783 | 
            +
            ```
         | 
| 784 | 
            +
            ```erb
         | 
| 785 | 
            +
            <%= link_to "Login via Facebook", "/auth/facebook" %>
         | 
| 786 | 
            +
            ```
         | 
| 787 | 
            +
             | 
| 788 | 
            +
            Let's implement the OmniAuth callback endpoint on our Rodauth controller:
         | 
| 789 | 
            +
             | 
| 790 | 
            +
            ```rb
         | 
| 791 | 
            +
            # config/routes.rb
         | 
| 792 | 
            +
            Rails.application.routes.draw do
         | 
| 793 | 
            +
              # ...
         | 
| 794 | 
            +
              get "/auth/:provider/callback", to: "rodauth#omniauth"
         | 
| 795 | 
            +
            end
         | 
| 796 | 
            +
            ```
         | 
| 797 | 
            +
            ```rb
         | 
| 798 | 
            +
            # app/controllres/rodauth_controller.rb
         | 
| 799 | 
            +
            class RodauthController < ApplicationController
         | 
| 800 | 
            +
              def omniauth
         | 
| 801 | 
            +
                auth = request.env["omniauth.auth"]
         | 
| 802 | 
            +
             | 
| 803 | 
            +
                # attempt to find existing identity directly
         | 
| 804 | 
            +
                identity = AccountIdentity.find_by(provider: auth["provider"], uid: auth["uid"])
         | 
| 805 | 
            +
             | 
| 806 | 
            +
                if identity
         | 
| 807 | 
            +
                  # update any external info changes
         | 
| 808 | 
            +
                  identity.update!(info: auth["info"])
         | 
| 809 | 
            +
                  # set account from identity
         | 
| 810 | 
            +
                  account = identity.account
         | 
| 811 | 
            +
                end
         | 
| 812 | 
            +
             | 
| 813 | 
            +
                # attempt to find an existing account by email
         | 
| 814 | 
            +
                account ||= Account.find_by(email: auth["info"]["email"])
         | 
| 815 | 
            +
             | 
| 816 | 
            +
                # disallow login if account is not verified
         | 
| 817 | 
            +
                if account && account.status != rodauth.account_open_status_value
         | 
| 818 | 
            +
                  redirect_to rodauth.login_path, alert: rodauth.unverified_account_message
         | 
| 819 | 
            +
                  return
         | 
| 820 | 
            +
                end
         | 
| 821 | 
            +
             | 
| 822 | 
            +
                # create new account if it doesn't exist
         | 
| 823 | 
            +
                unless account
         | 
| 824 | 
            +
                  account = Account.create!(email: auth["info"]["email"])
         | 
| 825 | 
            +
                end
         | 
| 826 | 
            +
             | 
| 827 | 
            +
                # create new identity if it doesn't exist
         | 
| 828 | 
            +
                unless identity
         | 
| 829 | 
            +
                  account.identities.create!(provider: auth["provider"], uid: auth["uid"], info: auth["info"])
         | 
| 830 | 
            +
                end
         | 
| 831 | 
            +
             | 
| 832 | 
            +
                # login with Rodauth
         | 
| 833 | 
            +
                rodauth.account_from_login(account.email)
         | 
| 834 | 
            +
                rodauth.login("omniauth")
         | 
| 835 | 
            +
              end
         | 
| 836 | 
            +
            end
         | 
| 837 | 
            +
            ```
         | 
| 838 | 
            +
             | 
| 567 839 | 
             
            ## Configuring
         | 
| 568 840 |  | 
| 569 841 | 
             
            For the list of configuration methods provided by Rodauth, see the [feature
         | 
| @@ -597,6 +869,37 @@ Rodauth::Rails.configure do |config| | |
| 597 869 | 
             
            end
         | 
| 598 870 | 
             
            ```
         | 
| 599 871 |  | 
| 872 | 
            +
            ## Custom extensions
         | 
| 873 | 
            +
             | 
| 874 | 
            +
            When developing custom extensions for Rodauth inside your Rails project, it's
         | 
| 875 | 
            +
            better to use plain modules (at least in the beginning), because Rodauth
         | 
| 876 | 
            +
            feature API doesn't yet support Zeitwerk reloading well.
         | 
| 877 | 
            +
             | 
| 878 | 
            +
            ```rb
         | 
| 879 | 
            +
            # app/lib/rodauth_argon2.rb
         | 
| 880 | 
            +
            module RodauthArgon2
         | 
| 881 | 
            +
              def password_hash(password)
         | 
| 882 | 
            +
                Argon2::Password.create(password, t_cost: password_hash_cost, m_cost: password_hash_cost)
         | 
| 883 | 
            +
              end
         | 
| 884 | 
            +
             | 
| 885 | 
            +
              def password_hash_match?(hash, password)
         | 
| 886 | 
            +
                Argon2::Password.verify_password(password, hash)
         | 
| 887 | 
            +
              end
         | 
| 888 | 
            +
            end
         | 
| 889 | 
            +
            ```
         | 
| 890 | 
            +
            ```rb
         | 
| 891 | 
            +
            # app/lib/rodauth_app.rb
         | 
| 892 | 
            +
            class RodauthApp < Rodauth::Rails::App
         | 
| 893 | 
            +
              configure do
         | 
| 894 | 
            +
                # ...
         | 
| 895 | 
            +
                auth_class_eval do
         | 
| 896 | 
            +
                  include RodauthArgon2
         | 
| 897 | 
            +
                end
         | 
| 898 | 
            +
                # ...
         | 
| 899 | 
            +
              end
         | 
| 900 | 
            +
            end
         | 
| 901 | 
            +
            ```
         | 
| 902 | 
            +
             | 
| 600 903 | 
             
            ## Testing
         | 
| 601 904 |  | 
| 602 905 | 
             
            If you're writing system tests, it's generally better to go through the actual
         | 
| @@ -669,6 +972,8 @@ Rodauth method for creating database functions: | |
| 669 972 |  | 
| 670 973 | 
             
            ```rb
         | 
| 671 974 | 
             
            # db/migrate/*_create_rodauth_database_functions.rb
         | 
| 975 | 
            +
            require "rodauth/migrations"
         | 
| 976 | 
            +
             | 
| 672 977 | 
             
            class CreateRodauthDatabaseFunctions < ActiveRecord::Migration
         | 
| 673 978 | 
             
              def up
         | 
| 674 979 | 
             
                Rodauth.create_database_authentication_functions(DB)
         | 
| @@ -733,7 +1038,6 @@ conduct](https://github.com/janko/rodauth-rails/blob/master/CODE_OF_CONDUCT.md). | |
| 733 1038 | 
             
            [Rodauth]: https://github.com/jeremyevans/rodauth
         | 
| 734 1039 | 
             
            [Sequel]: https://github.com/jeremyevans/sequel
         | 
| 735 1040 | 
             
            [feature documentation]: http://rodauth.jeremyevans.net/documentation.html
         | 
| 736 | 
            -
            [JWT feature]: http://rodauth.jeremyevans.net/rdoc/files/doc/jwt_rdoc.html
         | 
| 737 1041 | 
             
            [JWT gem]: https://github.com/jwt/ruby-jwt
         | 
| 738 1042 | 
             
            [Bootstrap]: https://getbootstrap.com/
         | 
| 739 1043 | 
             
            [Roda]: http://roda.jeremyevans.net/
         | 
| @@ -742,3 +1046,21 @@ conduct](https://github.com/janko/rodauth-rails/blob/master/CODE_OF_CONDUCT.md). | |
| 742 1046 | 
             
            [Rodauth migration]: http://rodauth.jeremyevans.net/rdoc/files/README_rdoc.html#label-Creating+tables
         | 
| 743 1047 | 
             
            [sequel-activerecord_connection]: https://github.com/janko/sequel-activerecord_connection
         | 
| 744 1048 | 
             
            [plugin options]: http://rodauth.jeremyevans.net/rdoc/files/README_rdoc.html#label-Plugin+Options
         | 
| 1049 | 
            +
            [hmac]: http://rodauth.jeremyevans.net/rdoc/files/README_rdoc.html#label-HMAC
         | 
| 1050 | 
            +
            [OmniAuth]: https://github.com/omniauth/omniauth
         | 
| 1051 | 
            +
            [otp]: http://rodauth.jeremyevans.net/rdoc/files/doc/otp_rdoc.html
         | 
| 1052 | 
            +
            [sms_codes]: http://rodauth.jeremyevans.net/rdoc/files/doc/sms_codes_rdoc.html
         | 
| 1053 | 
            +
            [recovery_codes]: http://rodauth.jeremyevans.net/rdoc/files/doc/recovery_codes_rdoc.html
         | 
| 1054 | 
            +
            [webauthn]: http://rodauth.jeremyevans.net/rdoc/files/doc/webauthn_rdoc.html
         | 
| 1055 | 
            +
            [json]: http://rodauth.jeremyevans.net/rdoc/files/doc/json_rdoc.html
         | 
| 1056 | 
            +
            [jwt]: http://rodauth.jeremyevans.net/rdoc/files/doc/jwt_rdoc.html
         | 
| 1057 | 
            +
            [email_auth]: http://rodauth.jeremyevans.net/rdoc/files/doc/email_auth_rdoc.html
         | 
| 1058 | 
            +
            [audit_logging]: http://rodauth.jeremyevans.net/rdoc/files/doc/audit_logging_rdoc.html
         | 
| 1059 | 
            +
            [password protection]: https://github.com/jeremyevans/rodauth#label-Password+Hash+Access+Via+Database+Functions
         | 
| 1060 | 
            +
            [bruteforce tokens]: https://github.com/jeremyevans/rodauth#label-Tokens
         | 
| 1061 | 
            +
            [password_complexity]: http://rodauth.jeremyevans.net/rdoc/files/doc/password_complexity_rdoc.html
         | 
| 1062 | 
            +
            [disallow_password_reuse]: http://rodauth.jeremyevans.net/rdoc/files/doc/disallow_password_reuse_rdoc.html
         | 
| 1063 | 
            +
            [password_expiration]: http://rodauth.jeremyevans.net/rdoc/files/doc/password_expiration_rdoc.html
         | 
| 1064 | 
            +
            [session_expiration]: http://rodauth.jeremyevans.net/rdoc/files/doc/session_expiration_rdoc.html
         | 
| 1065 | 
            +
            [single_session]: http://rodauth.jeremyevans.net/rdoc/files/doc/single_session_rdoc.html
         | 
| 1066 | 
            +
            [account_expiration]: http://rodauth.jeremyevans.net/rdoc/files/doc/account_expiration_rdoc.html
         | 
| @@ -13,6 +13,9 @@ module Rodauth | |
| 13 13 | 
             
                    source_root "#{__dir__}/templates"
         | 
| 14 14 | 
             
                    namespace "rodauth:install"
         | 
| 15 15 |  | 
| 16 | 
            +
                    class_option :json, type: :boolean, desc: "Configure JSON support"
         | 
| 17 | 
            +
                    class_option :jwt, type: :boolean, desc: "Configure JWT support"
         | 
| 18 | 
            +
             | 
| 16 19 | 
             
                    def create_rodauth_migration
         | 
| 17 20 | 
             
                      return unless defined?(ActiveRecord::Base)
         | 
| 18 21 |  | 
| @@ -74,15 +77,17 @@ module Rodauth | |
| 74 77 | 
             
                      end
         | 
| 75 78 | 
             
                    end
         | 
| 76 79 |  | 
| 77 | 
            -
                    def  | 
| 78 | 
            -
                       | 
| 80 | 
            +
                    def json?
         | 
| 81 | 
            +
                      options[:json]
         | 
| 82 | 
            +
                    end
         | 
| 79 83 |  | 
| 80 | 
            -
             | 
| 84 | 
            +
                    def jwt?
         | 
| 85 | 
            +
                      options[:jwt] || Rodauth::Rails.api_only?
         | 
| 81 86 | 
             
                    end
         | 
| 82 87 |  | 
| 83 88 | 
             
                    def migration_features
         | 
| 84 89 | 
             
                      features = [:base, :reset_password, :verify_account, :verify_login_change]
         | 
| 85 | 
            -
                      features << :remember unless  | 
| 90 | 
            +
                      features << :remember unless jwt?
         | 
| 86 91 | 
             
                      features
         | 
| 87 92 | 
             
                    end
         | 
| 88 93 | 
             
                  end
         | 
| @@ -1,11 +1,11 @@ | |
| 1 1 | 
             
            class RodauthApp < Rodauth::Rails::App
         | 
| 2 | 
            -
              configure | 
| 2 | 
            +
              configure do
         | 
| 3 3 | 
             
                # List of authentication features that are loaded.
         | 
| 4 4 | 
             
                enable :create_account, :verify_account, :verify_account_grace_period,
         | 
| 5 | 
            -
                  :login, :logout | 
| 5 | 
            +
                  :login, :logout<%= ", :remember" unless jwt? %>,
         | 
| 6 6 | 
             
                  :reset_password, :change_password, :change_password_notify,
         | 
| 7 7 | 
             
                  :change_login, :verify_login_change,
         | 
| 8 | 
            -
                  :close_account
         | 
| 8 | 
            +
                  :close_account<%= ", :json" if json? %><%= ", :jwt" if jwt? %>
         | 
| 9 9 |  | 
| 10 10 | 
             
                # See the Rodauth documentation for the list of available config options:
         | 
| 11 11 | 
             
                # http://rodauth.jeremyevans.net/documentation.html
         | 
| @@ -14,6 +14,16 @@ class RodauthApp < Rodauth::Rails::App | |
| 14 14 | 
             
                # The secret key used for hashing public-facing tokens for various features.
         | 
| 15 15 | 
             
                # Defaults to Rails `secret_key_base`, but you can use your own secret key.
         | 
| 16 16 | 
             
                # hmac_secret "<%= SecureRandom.hex(64) %>"
         | 
| 17 | 
            +
            <% if jwt? -%>
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                # Set JWT secret, which is used to cryptographically protect the token.
         | 
| 20 | 
            +
                jwt_secret "<%= SecureRandom.hex(64) %>"
         | 
| 21 | 
            +
            <% end -%>
         | 
| 22 | 
            +
            <% if json? || jwt? -%>
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                # Accept only JSON requests.
         | 
| 25 | 
            +
                only_json? true
         | 
| 26 | 
            +
            <% end -%>
         | 
| 17 27 |  | 
| 18 28 | 
             
                # Specify the controller used for view rendering and CSRF verification.
         | 
| 19 29 | 
             
                rails_controller { RodauthController }
         | 
| @@ -42,18 +52,6 @@ class RodauthApp < Rodauth::Rails::App | |
| 42 52 |  | 
| 43 53 | 
             
                # Redirect to the app from login and registration pages if already logged in.
         | 
| 44 54 | 
             
                # already_logged_in { redirect login_redirect }
         | 
| 45 | 
            -
            <% if api_only? -%>
         | 
| 46 | 
            -
             | 
| 47 | 
            -
                # ==> JWT
         | 
| 48 | 
            -
                # Set JWT secret, which is used to cryptographically protect the token.
         | 
| 49 | 
            -
                jwt_secret "<%= SecureRandom.hex(64) %>"
         | 
| 50 | 
            -
             | 
| 51 | 
            -
                # Don't require login confirmation param.
         | 
| 52 | 
            -
                require_login_confirmation? false
         | 
| 53 | 
            -
             | 
| 54 | 
            -
                # Don't require password confirmation param.
         | 
| 55 | 
            -
                require_password_confirmation? false
         | 
| 56 | 
            -
            <% end -%>
         | 
| 57 55 |  | 
| 58 56 | 
             
                # ==> Emails
         | 
| 59 57 | 
             
                # Uncomment the lines below once you've imported mailer views.
         | 
| @@ -80,14 +78,14 @@ class RodauthApp < Rodauth::Rails::App | |
| 80 78 | 
             
                #   db.after_commit { email.deliver_later }
         | 
| 81 79 | 
             
                # end
         | 
| 82 80 |  | 
| 83 | 
            -
                # In the meantime you can tweak settings for emails created by Rodauth
         | 
| 81 | 
            +
                # In the meantime, you can tweak settings for emails created by Rodauth.
         | 
| 84 82 | 
             
                # email_subject_prefix "[MyApp] "
         | 
| 85 83 | 
             
                # email_from "noreply@myapp.com"
         | 
| 86 84 | 
             
                # send_email(&:deliver_later)
         | 
| 87 85 | 
             
                # reset_password_email_body { "Click here to reset your password: #{reset_password_email_link}" }
         | 
| 88 86 |  | 
| 89 87 | 
             
                # ==> Flash
         | 
| 90 | 
            -
            <% unless  | 
| 88 | 
            +
            <% unless json? || jwt? -%>
         | 
| 91 89 | 
             
                # Match flash keys with ones already used in the Rails app.
         | 
| 92 90 | 
             
                # flash_notice_key :success # default is :notice
         | 
| 93 91 | 
             
                # flash_error_key :error # default is :alert
         | 
| @@ -107,7 +105,7 @@ class RodauthApp < Rodauth::Rails::App | |
| 107 105 |  | 
| 108 106 | 
             
                # Change minimum number of password characters required when creating an account.
         | 
| 109 107 | 
             
                # password_minimum_length 8
         | 
| 110 | 
            -
            <% unless  | 
| 108 | 
            +
            <% unless jwt? -%>
         | 
| 111 109 |  | 
| 112 110 | 
             
                # ==> Remember Feature
         | 
| 113 111 | 
             
                # Remember all logged in users.
         | 
| @@ -128,13 +126,14 @@ class RodauthApp < Rodauth::Rails::App | |
| 128 126 |  | 
| 129 127 | 
             
                # Perform additional actions after the account is created.
         | 
| 130 128 | 
             
                # after_create_account do
         | 
| 131 | 
            -
                #   Profile.create!(account_id:  | 
| 129 | 
            +
                #   Profile.create!(account_id: account_id, name: param("name"))
         | 
| 132 130 | 
             
                # end
         | 
| 133 131 |  | 
| 134 132 | 
             
                # Do additional cleanup after the account is closed.
         | 
| 135 133 | 
             
                # after_close_account do
         | 
| 136 | 
            -
                #   Profile.find_by!(account_id:  | 
| 134 | 
            +
                #   Profile.find_by!(account_id: account_id).destroy
         | 
| 137 135 | 
             
                # end
         | 
| 136 | 
            +
            <% unless json? || jwt? -%>
         | 
| 138 137 |  | 
| 139 138 | 
             
                # ==> Redirects
         | 
| 140 139 | 
             
                # Redirect to home page after logout.
         | 
| @@ -145,6 +144,7 @@ class RodauthApp < Rodauth::Rails::App | |
| 145 144 |  | 
| 146 145 | 
             
                # Redirect to login page after password reset.
         | 
| 147 146 | 
             
                reset_password_redirect { login_path }
         | 
| 147 | 
            +
            <% end -%>
         | 
| 148 148 |  | 
| 149 149 | 
             
                # ==> Deadlines
         | 
| 150 150 | 
             
                # Change default deadlines for some actions.
         | 
| @@ -156,14 +156,13 @@ class RodauthApp < Rodauth::Rails::App | |
| 156 156 |  | 
| 157 157 | 
             
              # ==> Multiple configurations
         | 
| 158 158 | 
             
              # configure(:admin) do
         | 
| 159 | 
            -
              #   enable :http_basic_auth
         | 
| 160 | 
            -
              #
         | 
| 159 | 
            +
              #   enable :http_basic_auth # enable different set of features
         | 
| 161 160 | 
             
              #   prefix "/admin"
         | 
| 162 | 
            -
              #    | 
| 161 | 
            +
              #   session_key_prefix "admin_"
         | 
| 163 162 | 
             
              # end
         | 
| 164 163 |  | 
| 165 164 | 
             
              route do |r|
         | 
| 166 | 
            -
            <% unless  | 
| 165 | 
            +
            <% unless jwt? -%>
         | 
| 167 166 | 
             
                rodauth.load_memory # autologin remembered users
         | 
| 168 167 |  | 
| 169 168 | 
             
            <% end -%>
         | 
    
        data/lib/rodauth/rails.rb
    CHANGED
    
    | @@ -32,6 +32,26 @@ module Rodauth | |
| 32 32 | 
             
                    scope.rodauth(name)
         | 
| 33 33 | 
             
                  end
         | 
| 34 34 |  | 
| 35 | 
            +
                  if ::Rails.gem_version >= Gem::Version.new("5.2")
         | 
| 36 | 
            +
                    def secret_key_base
         | 
| 37 | 
            +
                      ::Rails.application.secret_key_base
         | 
| 38 | 
            +
                    end
         | 
| 39 | 
            +
                  else
         | 
| 40 | 
            +
                    def secret_key_base
         | 
| 41 | 
            +
                      ::Rails.application.secrets.secret_key_base
         | 
| 42 | 
            +
                    end
         | 
| 43 | 
            +
                  end
         | 
| 44 | 
            +
             | 
| 45 | 
            +
                  if ::Rails.gem_version >= Gem::Version.new("5.0")
         | 
| 46 | 
            +
                    def api_only?
         | 
| 47 | 
            +
                      ::Rails.application.config.api_only
         | 
| 48 | 
            +
                    end
         | 
| 49 | 
            +
                  else
         | 
| 50 | 
            +
                    def api_only?
         | 
| 51 | 
            +
                      false
         | 
| 52 | 
            +
                    end
         | 
| 53 | 
            +
                  end
         | 
| 54 | 
            +
             | 
| 35 55 | 
             
                  def configure
         | 
| 36 56 | 
             
                    yield self
         | 
| 37 57 | 
             
                  end
         | 
    
        data/lib/rodauth/rails/app.rb
    CHANGED
    
    | @@ -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
         | 
| @@ -10,13 +12,13 @@ module Rodauth | |
| 10 12 | 
             
                  plugin :hooks
         | 
| 11 13 | 
             
                  plugin :render, layout: false
         | 
| 12 14 |  | 
| 13 | 
            -
                   | 
| 14 | 
            -
                     | 
| 15 | 
            -
             | 
| 16 | 
            -
             | 
| 17 | 
            -
                    end
         | 
| 15 | 
            +
                  if defined?(ActionDispatch::Flash) # not in API-only mode
         | 
| 16 | 
            +
                    require "rodauth/rails/app/flash"
         | 
| 17 | 
            +
                    plugin Flash
         | 
| 18 | 
            +
                  end
         | 
| 18 19 |  | 
| 19 | 
            -
             | 
| 20 | 
            +
                  def self.configure(name = nil, **options, &block)
         | 
| 21 | 
            +
                    plugin :rodauth, name: name, csrf: false, flash: false, json: true, **options do
         | 
| 20 22 | 
             
                      # load the Rails integration
         | 
| 21 23 | 
             
                      enable :rails
         | 
| 22 24 |  | 
| @@ -27,7 +29,7 @@ module Rodauth | |
| 27 29 | 
             
                      set_deadline_values? true
         | 
| 28 30 |  | 
| 29 31 | 
             
                      # use HMACs for additional security
         | 
| 30 | 
            -
                      hmac_secret { ::Rails. | 
| 32 | 
            +
                      hmac_secret { Rodauth::Rails.secret_key_base }
         | 
| 31 33 |  | 
| 32 34 | 
             
                      # evaluate user configuration
         | 
| 33 35 | 
             
                      instance_exec(&block)
         | 
| @@ -30,10 +30,12 @@ module Rodauth | |
| 30 30 | 
             
                        rails_request.flash
         | 
| 31 31 | 
             
                      end
         | 
| 32 32 |  | 
| 33 | 
            -
                       | 
| 34 | 
            -
                         | 
| 33 | 
            +
                      if ActionPack.version >= Gem::Version.new("5.0")
         | 
| 34 | 
            +
                        def commit_flash
         | 
| 35 35 | 
             
                          rails_request.commit_flash
         | 
| 36 | 
            -
                         | 
| 36 | 
            +
                        end
         | 
| 37 | 
            +
                      else
         | 
| 38 | 
            +
                        def commit_flash
         | 
| 37 39 | 
             
                          # ActionPack 4.2 automatically commits flash
         | 
| 38 40 | 
             
                        end
         | 
| 39 41 | 
             
                      end
         | 
| @@ -26,7 +26,7 @@ module Rodauth | |
| 26 26 | 
             
                def render(page)
         | 
| 27 27 | 
             
                  rails_render(partial: page.tr("-", "_"), layout: false) ||
         | 
| 28 28 | 
             
                    rails_render(action: page.tr("-", "_"), layout: false) ||
         | 
| 29 | 
            -
                    super
         | 
| 29 | 
            +
                    super.html_safe
         | 
| 30 30 | 
             
                end
         | 
| 31 31 |  | 
| 32 32 | 
             
                # Render Rails CSRF tags in Rodauth templates.
         | 
| @@ -44,11 +44,25 @@ module Rodauth | |
| 44 44 | 
             
                  true
         | 
| 45 45 | 
             
                end
         | 
| 46 46 |  | 
| 47 | 
            +
                # Reset Rails session to protect from session fixation attacks.
         | 
| 48 | 
            +
                def clear_session
         | 
| 49 | 
            +
                  rails_controller_instance.reset_session
         | 
| 50 | 
            +
                end
         | 
| 51 | 
            +
             | 
| 47 52 | 
             
                # Default the flash error key to Rails' default :alert.
         | 
| 48 53 | 
             
                def flash_error_key
         | 
| 49 54 | 
             
                  :alert
         | 
| 50 55 | 
             
                end
         | 
| 51 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 | 
            +
             | 
| 52 66 | 
             
                private
         | 
| 53 67 |  | 
| 54 68 | 
             
                # Runs controller callbacks and rescue handlers around Rodauth actions.
         | 
| @@ -63,20 +77,22 @@ module Rodauth | |
| 63 77 |  | 
| 64 78 | 
             
                  if rails_controller_instance.performed?
         | 
| 65 79 | 
             
                    rails_controller_response
         | 
| 66 | 
            -
                   | 
| 80 | 
            +
                  elsif result
         | 
| 67 81 | 
             
                    result[1].merge!(rails_controller_instance.response.headers)
         | 
| 68 82 | 
             
                    throw :halt, result
         | 
| 83 | 
            +
                  else
         | 
| 84 | 
            +
                    result
         | 
| 69 85 | 
             
                  end
         | 
| 70 86 | 
             
                end
         | 
| 71 87 |  | 
| 72 88 | 
             
                # Runs any #(before|around|after)_action controller callbacks.
         | 
| 73 89 | 
             
                def rails_controller_callbacks
         | 
| 74 90 | 
             
                  # don't verify CSRF token as part of callbacks, Rodauth will do that
         | 
| 75 | 
            -
                   | 
| 91 | 
            +
                  rails_controller_forgery_protection { false }
         | 
| 76 92 |  | 
| 77 93 | 
             
                  rails_controller_instance.run_callbacks(:process_action) do
         | 
| 78 94 | 
             
                    # turn the setting back to default so that form tags generate CSRF tags
         | 
| 79 | 
            -
                     | 
| 95 | 
            +
                    rails_controller_forgery_protection { rails_controller.allow_forgery_protection }
         | 
| 80 96 |  | 
| 81 97 | 
             
                    yield
         | 
| 82 98 | 
             
                  end
         | 
| @@ -116,7 +132,7 @@ module Rodauth | |
| 116 132 |  | 
| 117 133 | 
             
                # Calls the Rails renderer, returning nil if a template is missing.
         | 
| 118 134 | 
             
                def rails_render(*args)
         | 
| 119 | 
            -
                  return if  | 
| 135 | 
            +
                  return if rails_api_controller?
         | 
| 120 136 |  | 
| 121 137 | 
             
                  rails_controller_instance.render_to_string(*args)
         | 
| 122 138 | 
             
                rescue ActionView::MissingTemplate
         | 
| @@ -143,6 +159,13 @@ module Rodauth | |
| 143 159 | 
             
                  rails_controller_instance.send(:form_authenticity_token)
         | 
| 144 160 | 
             
                end
         | 
| 145 161 |  | 
| 162 | 
            +
                # allows/disables forgery protection
         | 
| 163 | 
            +
                def rails_controller_forgery_protection(&value)
         | 
| 164 | 
            +
                  return if rails_api_controller?
         | 
| 165 | 
            +
             | 
| 166 | 
            +
                  rails_controller_instance.allow_forgery_protection = value.call
         | 
| 167 | 
            +
                end
         | 
| 168 | 
            +
             | 
| 146 169 | 
             
                # Instances of the configured controller with current request's env hash.
         | 
| 147 170 | 
             
                def _rails_controller_instance
         | 
| 148 171 | 
             
                  controller    = rails_controller.new
         | 
| @@ -154,27 +177,29 @@ module Rodauth | |
| 154 177 | 
             
                end
         | 
| 155 178 |  | 
| 156 179 | 
             
                if ActionPack.version >= Gem::Version.new("5.0")
         | 
| 157 | 
            -
                  # Controller class to use for view rendering, CSRF protection, and
         | 
| 158 | 
            -
                  # running any registered action callbacks and rescue_from handlers.
         | 
| 159 | 
            -
                  def rails_controller
         | 
| 160 | 
            -
                    only_json? ? ActionController::API : ActionController::Base
         | 
| 161 | 
            -
                  end
         | 
| 162 | 
            -
             | 
| 163 180 | 
             
                  def prepare_rails_controller(controller, rails_request)
         | 
| 164 181 | 
             
                    controller.set_request! rails_request
         | 
| 165 182 | 
             
                    controller.set_response! rails_controller.make_response!(rails_request)
         | 
| 166 183 | 
             
                  end
         | 
| 167 184 | 
             
                else
         | 
| 168 | 
            -
                  def rails_controller
         | 
| 169 | 
            -
                    ActionController::Base
         | 
| 170 | 
            -
                  end
         | 
| 171 | 
            -
             | 
| 172 185 | 
             
                  def prepare_rails_controller(controller, rails_request)
         | 
| 173 186 | 
             
                    controller.send(:set_response!, rails_request)
         | 
| 174 187 | 
             
                    controller.instance_variable_set(:@_request, rails_request)
         | 
| 175 188 | 
             
                  end
         | 
| 176 189 | 
             
                end
         | 
| 177 190 |  | 
| 191 | 
            +
                def rails_api_controller?
         | 
| 192 | 
            +
                  defined?(ActionController::API) && rails_controller <= ActionController::API
         | 
| 193 | 
            +
                end
         | 
| 194 | 
            +
             | 
| 195 | 
            +
                def rails_controller
         | 
| 196 | 
            +
                  if only_json? && Rodauth::Rails.api_only?
         | 
| 197 | 
            +
                    ActionController::API
         | 
| 198 | 
            +
                  else
         | 
| 199 | 
            +
                    ActionController::Base
         | 
| 200 | 
            +
                  end
         | 
| 201 | 
            +
                end
         | 
| 202 | 
            +
             | 
| 178 203 | 
             
                # ActionMailer subclass for correct email delivering.
         | 
| 179 204 | 
             
                class Mailer < ActionMailer::Base
         | 
| 180 205 | 
             
                  def create_email(**options)
         | 
    
        data/rodauth-rails.gemspec
    CHANGED
    
    | @@ -17,8 +17,10 @@ Gem::Specification.new do |spec| | |
| 17 17 | 
             
              spec.require_paths = ["lib"]
         | 
| 18 18 |  | 
| 19 19 | 
             
              spec.add_dependency "railties", ">= 4.2", "< 7"
         | 
| 20 | 
            -
              spec.add_dependency "rodauth", "~> 2. | 
| 20 | 
            +
              spec.add_dependency "rodauth", "~> 2.9"
         | 
| 21 21 | 
             
              spec.add_dependency "sequel-activerecord_connection", "~> 1.1"
         | 
| 22 22 | 
             
              spec.add_dependency "tilt"
         | 
| 23 23 | 
             
              spec.add_dependency "bcrypt"
         | 
| 24 | 
            +
             | 
| 25 | 
            +
              spec.add_development_dependency "jwt"
         | 
| 24 26 | 
             
            end
         | 
    
        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.9.0
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors:
         | 
| 7 7 | 
             
            - Janko Marohnić
         | 
| 8 | 
            -
            autorequire: | 
| 8 | 
            +
            autorequire:
         | 
| 9 9 | 
             
            bindir: bin
         | 
| 10 10 | 
             
            cert_chain: []
         | 
| 11 | 
            -
            date:  | 
| 11 | 
            +
            date: 2021-02-07 00:00:00.000000000 Z
         | 
| 12 12 | 
             
            dependencies:
         | 
| 13 13 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 14 14 | 
             
              name: railties
         | 
| @@ -36,14 +36,14 @@ dependencies: | |
| 36 36 | 
             
                requirements:
         | 
| 37 37 | 
             
                - - "~>"
         | 
| 38 38 | 
             
                  - !ruby/object:Gem::Version
         | 
| 39 | 
            -
                    version: '2. | 
| 39 | 
            +
                    version: '2.9'
         | 
| 40 40 | 
             
              type: :runtime
         | 
| 41 41 | 
             
              prerelease: false
         | 
| 42 42 | 
             
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 43 43 | 
             
                requirements:
         | 
| 44 44 | 
             
                - - "~>"
         | 
| 45 45 | 
             
                  - !ruby/object:Gem::Version
         | 
| 46 | 
            -
                    version: '2. | 
| 46 | 
            +
                    version: '2.9'
         | 
| 47 47 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 48 48 | 
             
              name: sequel-activerecord_connection
         | 
| 49 49 | 
             
              requirement: !ruby/object:Gem::Requirement
         | 
| @@ -86,6 +86,20 @@ dependencies: | |
| 86 86 | 
             
                - - ">="
         | 
| 87 87 | 
             
                  - !ruby/object:Gem::Version
         | 
| 88 88 | 
             
                    version: '0'
         | 
| 89 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 90 | 
            +
              name: jwt
         | 
| 91 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 92 | 
            +
                requirements:
         | 
| 93 | 
            +
                - - ">="
         | 
| 94 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 95 | 
            +
                    version: '0'
         | 
| 96 | 
            +
              type: :development
         | 
| 97 | 
            +
              prerelease: false
         | 
| 98 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 99 | 
            +
                requirements:
         | 
| 100 | 
            +
                - - ">="
         | 
| 101 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 102 | 
            +
                    version: '0'
         | 
| 89 103 | 
             
            description: Provides Rails integration for Rodauth.
         | 
| 90 104 | 
             
            email:
         | 
| 91 105 | 
             
            - janko.marohnic@gmail.com
         | 
| @@ -187,7 +201,6 @@ files: | |
| 187 201 | 
             
            - lib/generators/rodauth/templates/db/migrate/create_rodauth.rb
         | 
| 188 202 | 
             
            - lib/generators/rodauth/views_generator.rb
         | 
| 189 203 | 
             
            - lib/rodauth-rails.rb
         | 
| 190 | 
            -
            - lib/rodauth/features/rails.rb
         | 
| 191 204 | 
             
            - lib/rodauth/rails.rb
         | 
| 192 205 | 
             
            - lib/rodauth/rails/app.rb
         | 
| 193 206 | 
             
            - lib/rodauth/rails/app/flash.rb
         | 
| @@ -203,7 +216,7 @@ homepage: https://github.com/janko/rodauth-rails | |
| 203 216 | 
             
            licenses:
         | 
| 204 217 | 
             
            - MIT
         | 
| 205 218 | 
             
            metadata: {}
         | 
| 206 | 
            -
            post_install_message: | 
| 219 | 
            +
            post_install_message:
         | 
| 207 220 | 
             
            rdoc_options: []
         | 
| 208 221 | 
             
            require_paths:
         | 
| 209 222 | 
             
            - lib
         | 
| @@ -218,8 +231,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement | |
| 218 231 | 
             
                - !ruby/object:Gem::Version
         | 
| 219 232 | 
             
                  version: '0'
         | 
| 220 233 | 
             
            requirements: []
         | 
| 221 | 
            -
            rubygems_version: 3. | 
| 222 | 
            -
            signing_key: | 
| 234 | 
            +
            rubygems_version: 3.2.3
         | 
| 235 | 
            +
            signing_key:
         | 
| 223 236 | 
             
            specification_version: 4
         | 
| 224 237 | 
             
            summary: Provides Rails integration for Rodauth.
         | 
| 225 238 | 
             
            test_files: []
         | 
| @@ -1 +0,0 @@ | |
| 1 | 
            -
            require "rodauth/rails/feature"
         |