Git Product home page Git Product logo

rodauth-rails's Introduction

rodauth-rails

Provides Rails integration for the Rodauth authentication framework.

Resources

๐Ÿ”— Useful links:

๐ŸŽฅ Screencasts:

๐Ÿ“š Articles:

Why Rodauth?

There are already several popular authentication solutions for Rails (Devise, Sorcery, Clearance, Authlogic), so why would you choose Rodauth? Here are some of the advantages that stand out for me:

Sequel

One common concern for people coming from other Rails authentication frameworks is the fact that Rodauth uses Sequel for database interaction instead of Active Record. For Rails apps using Active Record, rodauth-rails configures Sequel to reuse Active Record's database connection. This makes it run smoothly alongside Active Record, even allowing calling Active Record code from within Rodauth configuration. So, for all intents and purposes, Sequel can be treated just as an implementation detail of Rodauth.

Installation

Add the gem to your project:

$ bundle add rodauth-rails

Next, run the install generator:

$ rails generate rodauth:install

This generator will create a Rodauth app and configuration with common authentication features enabled, a database migration with tables required by those features, and a few other files.

Feel free to remove any features you don't need, along with their corresponding tables. Afterwards, run the migration:

$ rails db:migrate

Install options

The install generator will use the accounts table by default. You can specify a different table name:

$ rails generate rodauth:install users

If you want Rodauth endpoints to be exposed via JSON API:

$ rails generate rodauth:install --json # regular authentication using the Rails session
# or
$ rails generate rodauth:install --jwt # token authentication via the "Authorization" header
$ bundle add jwt

To use Argon2 instead of bcrypt for password hashing:

$ rails generate rodauth:install --argon2
$ bundle add argon2

Usage

The Rodauth app will be called for each request before it reaches the Rails router. It handles requests to Rodauth endpoints, and allows you to call additional code before your main routes.

$ rails middleware
# ...
# use Rodauth::Rails::Middleware (calls your Rodauth app)
# run YourApp::Application.routes

Routes

Because requests to Rodauth endpoints are handled by Roda, Rodauth routes will not show in rails routes. You can use the rodauth:routes rake task to view the list of endpoints based on currently loaded features:

$ rails rodauth:routes
Routes handled by RodauthApp:

  GET|POST  /login                   rodauth.login_path
  GET|POST  /create-account          rodauth.create_account_path
  GET|POST  /verify-account-resend   rodauth.verify_account_resend_path
  GET|POST  /verify-account          rodauth.verify_account_path
  GET|POST  /change-password         rodauth.change_password_path
  GET|POST  /change-login            rodauth.change_login_path
  GET|POST  /logout                  rodauth.logout_path
  GET|POST  /remember                rodauth.remember_path
  GET|POST  /reset-password-request  rodauth.reset_password_request_path
  GET|POST  /reset-password          rodauth.reset_password_path
  GET|POST  /verify-login-change     rodauth.verify_login_change_path
  GET|POST  /close-account           rodauth.close_account_path

Using this information, you can add some basic authentication links to your navigation header:

<% if rodauth.logged_in? %>
  <%= button_to "Sign out", rodauth.logout_path, method: :post %>
<% else %>
  <%= link_to "Sign in", rodauth.login_path %>
  <%= link_to "Sign up", rodauth.create_account_path %>
<% end %>

These routes are fully functional, feel free to visit them and interact with the pages. The templates that ship with Rodauth aim to provide a complete authentication experience, and the forms use Bootstrap markup.

Current account

The Rodauth object defines a #rails_account method, which returns a model instance of the currently logged in account. You can create a helper method for easy access from controllers and views:

class ApplicationController < ActionController::Base
  private

  def current_account
    rodauth.rails_account
  end
  helper_method :current_account # skip if inheriting from ActionController::API
end

Requiring authentication

You can require authentication for routes at the middleware level in in your Rodauth app's routing block, which helps keep the authentication logic encapsulated:

# app/misc/rodauth_app.rb
class RodauthApp < Rodauth::Rails::App
  route do |r|
    r.rodauth # route rodauth requests

    if r.path.start_with?("/dashboard") # /dashboard/* routes
      rodauth.require_account # redirect to login page if not authenticated
    end
  end
end

You can also require authentication at the controller layer:

class ApplicationController < ActionController::Base
  private

  def authenticate
    rodauth.require_account # redirect to login page if not authenticated
  end
end
class DashboardController < ApplicationController
  before_action :authenticate
end

Additionally, routes can be authenticated at the Rails router level:

# config/routes.rb
Rails.application.routes.draw do
  constraints Rodauth::Rails.authenticate do
    # ... these routes will require authentication ...
  end

  constraints Rodauth::Rails.authenticate { |rodauth| rodauth.uses_two_factor_authentication? } do
    # ... these routes will be available only if 2FA is setup ...
  end

  constraints Rodauth::Rails.authenticate(:admin) do
    # ... these routes will be authenticated with secondary "admin" configuration ...
  end

  constraints -> (r) { !r.env["rodauth"].logged_in? } do # or env["rodauth.admin"]
    # ... these routes will be available only if not authenticated ...
  end
end

Controller

Your Rodauth configuration is linked to a Rails controller, which is primarily used to render views and handle CSRF protection, but will also execute any callbacks and rescue handlers defined on it around Rodauth endpoints.

# app/misc/rodauth_main.rb
class RodauthMain < Rodauth::Rails::Auth
  configure do
    rails_controller { RodauthController }
  end
end
class RodauthController < ApplicationController
  before_action :verify_captcha, only: :login, if: -> { request.post? } # executes before Rodauth endpoints
  rescue_from("SomeError") { |exception| ... } # rescues around Rodauth endpoints
end

Various methods are available in your Rodauth configuration to bridge the gap with the controller:

class RodauthMain < Rodauth::Rails::Auth
  configure do
    # calling methods on the controller:
    after_create_account do
      rails_controller_eval { some_controller_method(account_id) }
    end

    # accessing Rails URL helpers:
    login_redirect { rails_routes.dashboard_path }

    # accessing Rails request object:
    after_change_password do
      if rails_request.format.turbo_stream?
        return_response rails_render(turbo_stream: [turbo_stream.replace(...)])
      end
    end

    # accessing Rails cookies:
    after_login { rails_cookies.permanent[:last_account_id] = account_id }
  end
end

Views

The templates built into Rodauth are useful when getting started, but soon you'll want to start editing the markup. You can run the following command to copy Rodauth templates into your Rails app:

$ rails generate rodauth:views

This will generate views for Rodauth features you have currently enabled into the app/views/rodauth directory (provided that RodauthController is set for the main configuration).

The generator accepts various options:

# generate views with Tailwind markup (requires @tailwindcss/forms plugin)
$ rails generate rodauth:views --css=tailwind

# specify Rodauth features to generate views for
$ rails generate rodauth:views login create_account lockout otp

# generate views for all Rodauth features
$ rails generate rodauth:views --all

# specify a different Rodauth configuration
$ rails generate rodauth:views webauthn two_factor_base --name admin

Mailer

When you're ready to modify the default email templates and safely deliver them in a background job, you can run the following command to generate the mailer integration:

$ rails generate rodauth:mailer

This will create a RodauthMailer, email templates, and necessary Rodauth configuration for the features you have enabled. For email links to work, you need to have config.action_mailer.default_url_options set for each environment.

# config/environments/development.rb
config.action_mailer.default_url_options = { host: "localhost", port: 3000 }

The generator accepts various options:

# generate mailer integration for specified features
$ rails generate rodauth:mailer email_auth lockout webauthn_modify_email

# generate mailer integration for all Rodauth features
$ rails generate rodauth:mailer --all

# specify different Rodauth configuration to select enabled features
$ rails generate rodauth:mailer --name admin

Note that the generated Rodauth configuration calls #deliver_later, which uses Active Job to deliver emails in a background job. If you want to deliver emails synchronously, you can modify the configuration to call #deliver_now instead.

If you're using a background processing library without an Active Job adapter, or a 3rd-party service for sending transactional emails, see this wiki page on how to set it up.

Migrations

The install generator will create a migration for tables used by the Rodauth features enabled by default. For any additional features, you can use the migration generator to create the required tables:

$ rails generate rodauth:migration otp sms_codes recovery_codes
# db/migration/*_create_rodauth_otp_sms_codes_recovery_codes.rb
class CreateRodauthOtpSmsCodesRecoveryCodes < ActiveRecord::Migration
  def change
    create_table :account_otp_keys do |t| ... end
    create_table :account_sms_codes do |t| ... end
    create_table :account_recovery_codes do |t| ... end
  end
end

If you're storing account records in a table other than accounts, you'll want to specify the appropriate table prefix when generating new migrations:

$ rails generate rodauth:migration base active_sessions --prefix user

# Add the following to your Rodauth configuration:
#
#   accounts_table :users
#   active_sessions_table :user_active_session_keys
#   active_sessions_account_id_column :user_id
# db/migration/*_create_rodauth_user_base_active_sessions.rb
class CreateRodauthUserBaseActiveSessions < ActiveRecord::Migration
  def change
    create_table :users do |t| ... end
    create_table :user_active_session_keys do |t| ... end
  end
end

You can change the default migration name:

$ rails generate rodauth:migration email_auth --name create_account_email_auth_keys
# db/migration/*_create_account_email_auth_keys.rb
class CreateAccountEmailAuthKeys < ActiveRecord::Migration
  def change
    create_table :account_email_auth_keys do |t| ... end
  end
end

Model

The rodauth-model gem provides a Rodauth::Model mixin that can be included into the account model, which defines a password attribute and associations for tables used by enabled authentication features.

class Account < ActiveRecord::Base # Sequel::Model
  include Rodauth::Rails.model # or `Rodauth::Rails.model(:admin)`
end
# setting password hash
account = Account.create!(email: "[email protected]", password: "secret123")
account.password_hash #=> "$2a$12$k/Ub1I2iomi84RacqY89Hu4.M0vK7klRnRtzorDyvOkVI.hKhkNw."

# clearing password hash
account.password = nil
account.password_hash #=> nil

# associations
account.remember_key #=> #<Account::RememberKey> (record from `account_remember_keys` table)
account.active_session_keys #=> [#<Account::ActiveSessionKey>,...] (records from `account_active_session_keys` table)

Multiple configurations

If you need to handle multiple types of accounts that require different authentication logic, you can create new configurations for them. This is done by creating new Rodauth::Rails::Auth subclasses, and registering them under a name.

# app/misc/rodauth_app.rb
class RodauthApp < Rodauth::Rails::App
  configure RodauthMain          # primary configuration
  configure RodauthAdmin, :admin # secondary configuration

  route do |r|
    r.rodauth         # route primary rodauth requests
    r.rodauth(:admin) # route secondary rodauth requests

    if request.path.start_with?("/admin")
      rodauth(:admin).require_account
    end
  end
end
# app/misc/rodauth_admin.rb
class RodauthAdmin < Rodauth::Rails::Auth
  configure do
    # ... enable features ...
    prefix "/admin"
    session_key_prefix "admin_"
    remember_cookie_key "_admin_remember" # if using remember feature

    # search views in `app/views/admin/rodauth` directory
    rails_controller { Admin::RodauthController }
  end
end
# app/controllers/admin/rodauth_controller.rb
class Admin::RodauthController < ApplicationController
end

Then in your application you can reference the secondary Rodauth instance:

rodauth(:admin).authenticated? # checks "admin_account_id" session value
rodauth(:admin).login_path #=> "/admin/login"

You'll likely want to save the information of which account belongs to which configuration to the database, see this guide on how you can do that. Note that you can also share configuration via inheritance.

Outside of a request

The internal_request and path_class_methods features are supported, with defaults taken from config.action_mailer.default_url_options.

# internal requests
RodauthApp.rodauth.create_account(login: "[email protected]", password: "secret123")
RodauthApp.rodauth(:admin).verify_account(account_login: "[email protected]")

# path and URL methods
RodauthApp.rodauth.close_account_path #=> "/close-account"
RodauthApp.rodauth(:admin).otp_setup_url #=> "http://localhost:3000/admin/otp-setup"

Calling instance methods

If you need to access Rodauth methods not exposed as internal requests, you can use Rodauth::Rails.rodauth to retrieve the Rodauth instance (this requires enabling the internal_request feature):

account = Account.find_by!(email: "[email protected]")
rodauth = Rodauth::Rails.rodauth(account: account) #=> #<RodauthMain::InternalRequest ...>

rodauth.compute_hmac("token") #=> "TpEJTKfKwqYvIDKWsuZhkhKlhaBXtR1aodskBAflD8U"
rodauth.open_account? #=> true
rodauth.two_factor_authentication_setup? #=> true
rodauth.password_meets_requirements?("foo") #=> false
rodauth.locked_out? #=> false

In addition to the :account option, the Rodauth::Rails.rodauth method accepts any options supported by the internal_request feature.

# main configuration
Rodauth::Rails.rodauth(env: { "HTTP_USER_AGENT" => "programmatic" })
Rodauth::Rails.rodauth(session: { two_factor_auth_setup: true })

# secondary configuration
Rodauth::Rails.rodauth(:admin, params: { "param" => "value" })

You can override default URL options ad-hoc by modifying #rails_url_options:

rodauth.base_url #=> "https://example.com"
rodauth.rails_url_options[:host] = "subdomain.example.com"
rodauth.base_url #=> "https://subdomain.example.com"

Using as a library

Rodauth offers a Rodauth.lib method for when you want to use it as a library (via internal requests), as opposed to having it route requests. This gem provides a Rodauth::Rails.lib counterpart that does the same but with Rails integration:

# skip require on boot to avoid inserting Rodauth middleware
gem "rodauth-rails", require: false
# app/misc/rodauth_main.rb
require "rodauth/rails"
require "sequel/core"

RodauthMain = Rodauth::Rails.lib do
  enable :create_account, :login, :close_account
  db Sequel.postgres(extensions: :activerecord_connection, keep_reference: false)
  # ...
end
RodauthMain.create_account(login: "[email protected]", password: "secret123")
RodauthMain.login(login: "[email protected]", password: "secret123")
RodauthMain.close_account(account_login: "[email protected]")

Testing

For system and integration tests, which run the whole middleware stack, authentication can be exercised normally via HTTP endpoints. For example, given a controller

# app/controllers/articles_controller.rb
class ArticlesController < ApplicationController
  before_action -> { rodauth.require_account }

  def index
    # ...
  end
end

One can write ActionDispatch::IntegrationTest test helpers for login and logout by making requests to the Rodauth endpoints:

# test/controllers/articles_controller_test.rb
class ArticlesControllerTest < ActionDispatch::IntegrationTest
  def login(email, password)
    post "/login", params: { email: email, password: password }
    assert_redirected_to "/"
  end

  def logout
    post "/logout"
    assert_redirected_to "/"
  end

  test "required authentication" do
    get :index

    assert_response 302
    assert_redirected_to "/login"
    assert_equal "Please login to continue", flash[:alert]

    account = Account.create!(email: "[email protected]", password: "secret123", status: "verified")
    login(account.email, "secret123")

    get :index
    assert_response 200

    logout

    get :index
    assert_response 302
    assert_equal "Please login to continue", flash[:alert]
  end
end

For more examples and information about testing with rodauth, see this wiki page about testing.

Configuring

The rails feature rodauth-rails loads provides the following configuration methods:

Name Description
rails_render(**options) Renders the template with given render options.
rails_csrf_tag Hidden field added to Rodauth templates containing the CSRF token.
rails_csrf_param Value of the name attribute for the CSRF tag.
rails_csrf_token Value of the value attribute for the CSRF tag.
rails_check_csrf! Verifies the authenticity token for the current request.
rails_controller_instance Instance of the controller with the request env context.
rails_controller Controller class to use for rendering and CSRF protection.
rails_account_model Model class connected with the accounts table.
rails_url_options Options used for generating URLs outside of a request (defaults to config.action_mailer.default_url_options)
class RodauthMain < Rodauth::Rails::Auth
  configure do
    rails_account_model { MyApp::Account }
    rails_controller { MyApp::RodauthController }
  end
end

Manually inserting middleware

You can choose to insert the Rodauth middleware somewhere earlier than in front of the Rails router:

Rodauth::Rails.configure do |config|
  config.middleware = false # disable auto-insertion
end

Rails.configuration.middleware.insert_before AnotherMiddleware, Rodauth::Rails::Middleware

How it works

Rack middleware

The railtie inserts Rodauth::Rails::Middleware at the end of the middleware stack, which is just a wrapper around your Rodauth app.

$ rails middleware
# ...
# use Rodauth::Rails::Middleware
# run MyApp::Application.routes

Roda app

The Rodauth::Rails::App class is a Roda subclass that provides a convenience layer over Rodauth.

Configure block

The configure call is a wrapper around plugin :rodauth. By convention, it receives an auth class and configuration name as positional arguments (which get converted into :auth_class and :name plugin options), a block for anonymous auth classes, and also accepts any additional plugin options.

class RodauthApp < Rodauth::Rails::App
  # named auth class
  configure(RodauthMain)
  configure(RodauthAdmin, :admin)

  # anonymous auth class
  configure { ... }
  configure(:admin) { ... }

  # plugin options
  configure(RodauthMain, json: :only, render: false)
end

Route block

The route block is called for each request, before it reaches the Rails router, and it's yielded the request object.

class RodauthApp < Rodauth::Rails::App
  route do |r|
    # called before each request
  end
end

Rack env

The app sets Rodauth objects for each registered configuration in the Rack env, so that they're accessible downstream by the Rails router, controllers and views:

request.env["rodauth"]       #=> #<RodauthMain>
request.env["rodauth.admin"] #=> #<RodauthAdmin> (if using multiple configurations)

Auth class

The Rodauth::Rails::Auth class is a subclass of Rodauth::Auth, which preloads the rails rodauth feature, sets HMAC secret to Rails' secret key base, and modifies some configuration defaults.

class RodauthMain < Rodauth::Rails::Auth
  configure do
    # authentication configuration
  end
end

Rodauth feature

The rails Rodauth feature loaded by Rodauth::Rails::Auth provides the main part of the Rails integration for Rodauth:

  • uses Action View for template rendering
  • uses Action Dispatch for CSRF protection
  • runs Action Controller callbacks and rescue from blocks around Rodauth requests
  • uses Action Mailer to create and deliver emails
  • uses Action Controller instrumentation around Rodauth requests
  • uses Action Mailer's default URL options when calling Rodauth outside of a request

License

The gem is available as open source under the terms of the MIT License.

Code of Conduct

Everyone interacting in the rodauth-rails project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the code of conduct.

rodauth-rails's People

Contributors

basabin54 avatar benkoshy avatar conradbeach avatar dmitryzuev avatar dush avatar empact avatar honeyryderchuck avatar igor-alexandrov avatar intrepidd avatar janko avatar longthanhtran avatar nicolas-besnard avatar rmacklin avatar samuelodan avatar soulcutter avatar thedumbtechguy avatar vlado avatar zavan avatar zhongsheng avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

rodauth-rails's Issues

Jwt-refresh not working

Hello!
I am testing this gem for rails. I want to use it for my graphql api and rails views. I enabled the json and jwt features, along with active_sessions, lockout and jwt_refresh.

Then, I tested the endpoint for login and it worked fine.

POST http://localhost:3000/login
Content-Type: application/json

{
    "login": "[email protected]",
    "password": "123456"
}

I get:

HTTP/1.1 200 OK
Authorization: eyJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2MzIxODA1ODEsImlhdCI6MTYzMjE3ODc4MSwibmJmIjoxNjMyMTc4Nzc2LCJhY2NvdW50X2lkIjoxLCJ1bnZlcmlmaWVkX2FjY291bnQiOnRydWUsImFjdGl2ZV9zZXNzaW9uX2lkIjoiTExGYm4wV2JxU2dqSmFkVzVRYk9xbTE2aXh5dkZRQXVnZk9MUjdPbnZWSSIsImF1dGhlbnRpY2F0ZWRfYnkiOlsicGFzc3dvcmQiXX0.DGyhJhxA2p7YYNTC0mNGuylJotSc2hdZGRwCYRAdWcw
Content-Type: application/json

{
  "access_token": "eyJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2MzIxODA1ODEsImlhdCI6MTYzMjE3ODc4MSwibmJmIjoxNjMyMTc4Nzc2LCJhY2NvdW50X2lkIjoxLCJ1bnZlcmlmaWVkX2FjY291bnQiOnRydWUsImFjdGl2ZV9zZXNzaW9uX2lkIjoiTExGYm4wV2JxU2dqSmFkVzVRYk9xbTE2aXh5dkZRQXVnZk9MUjdPbnZWSSIsImF1dGhlbnRpY2F0ZWRfYnkiOlsicGFzc3dvcmQiXX0.DGyhJhxA2p7YYNTC0mNGuylJotSc2hdZGRwCYRAdWcw",
  "refresh_token": "1_3_blZz9zqJrey8LRjyI1FVWxqZ05ZJ0guovymO3dzJ-3M",
  "success": "You have been logged in"
}

But using refresh token tells me that the refresh_token is invalid.

POST http://localhost:3000/jwt-refresh
Content-Type: application/json
Authorization: eyJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2MzIxODA1ODEsImlhdCI6MTYzMjE3ODc4MSwibmJmIjoxNjMyMTc4Nzc2LCJhY2NvdW50X2lkIjoxLCJ1bnZlcmlmaWVkX2FjY291bnQiOnRydWUsImFjdGl2ZV9zZXNzaW9uX2lkIjoiTExGYm4wV2JxU2dqSmFkVzVRYk9xbTE2aXh5dkZRQXVnZk9MUjdPbnZWSSIsImF1dGhlbnRpY2F0ZWRfYnkiOlsicGFzc3dvcmQiXX0.DGyhJhxA2p7YYNTC0mNGuylJotSc2hdZGRwCYRAdWcw

{
  "refresh_token": "1_3_blZz9zqJrey8LRjyI1FVWxqZ05ZJ0guovymO3dzJ-3M"
}

I get:

HTTP/1.1 400 Bad Request
Content-Type: application/json

{
  "error": "invalid JWT refresh token"
}

What am I doing wrong? Or is it a issue?

Thanks!

Support rails 7.0

rodauth-rails depends on "railties", ">= 4.2", "< 7" so app using it can't be upgraded to rails ~> 7.0.0

RodauthApp#call as TURBO_STREAM

ArgumentError (There was no default layout for RodauthController...

this happen when session expires and it does not redirect to login page, any comment?

No ActiveRecord, Still complains about having it...

I have an existing project that composed by these:

rails@6.1.4.1
sequel
sequel-rails
rodauth-rails # added this
devise # removed favor of rodauth-rails
devise-sequel # removed favor of rodauth-rails
doorkeeper # removed favor of rodauth-oauth
doorkeeper-sequel # removed favor of rodauth-oauth

So I gone ahead and dumped Devise + Doorkeeper, However, I also have this setup in my application.rb

# require 'rails/all'
# require 'active_model/railtie'
# require 'active_job/railtie'
# require "active_record/railtie"
require 'action_controller/railtie'
require 'action_mailer/railtie'
require 'action_view/railtie'
# require "action_cable/engine"
require 'sprockets/railtie'
require 'rails/test_unit/railtie'
require 'rexml/document'

As you can see, I am not using ActiveRecord at all. Instead i'm using sequel-rails to bring Sequel to the Rails...

Problem starts here, I cannot execute rodauth:install. It hangs on ActiveRecord step and says db is unreachable. If I manually dump ActiveRecord via Object.send(:remove_const, :ActiveRecord) - which is not recommended way of doing this - then I get ActiveModel issues.

Basically, either one of;

a. rodauth-rails forcefully requires ActiveRecord
b. Does not work with sequel-rails
c. Something is wrong with the existing project (still searching, tho.)

Do you have any idea? Should I get rid of sequel-rails and use ActiveRecord as-is? But the whole project is depends on Sequel and has no any ActiveRecord-related content at all.

Some logs:

/home/gencer/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/activerecord-6.1.3.2/lib/active_record/connection_handling.rb:323:in `connection_pool': ActiveRecord::ConnectionNotEstablished (ActiveRecord::ConnectionNotEstablished)
        from /home/gencer/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/activerecord-6.1.3.2/lib/active_record/connection_handling.rb:319:in `connection_db_config'
        from /home/gencer/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/rodauth-rails-0.18.0/lib/generators/rodauth/migration_helpers.rb:28:in `activerecord_adapter'
        from (erb):1:in `erb_eval'
        from /home/gencer/.rbenv/versions/3.0.1/lib/ruby/3.0.0/erb.rb:905:in `eval'
        from /home/gencer/.rbenv/versions/3.0.1/lib/ruby/3.0.0/erb.rb:905:in `result'
        from /home/gencer/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/rodauth-rails-0.18.0/lib/generators/rodauth/migration_helpers.rb:61:in `erb_eval'
        from /home/gencer/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/rodauth-rails-0.18.0/lib/generators/rodauth/migration_helpers.rb:21:in `block in migration_content'
        from /home/gencer/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/rodauth-rails-0.18.0/lib/generators/rodauth/migration_helpers.rb:21:in `map'
        from /home/gencer/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/rodauth-rails-0.18.0/lib/generators/rodauth/migration_helpers.rb:21:in `migration_content'
        from (erb):3:in `migration_template'
        from /home/gencer/.rbenv/versions/3.0.1/lib/ruby/3.0.0/erb.rb:905:in `eval'
        from /home/gencer/.rbenv/versions/3.0.1/lib/ruby/3.0.0/erb.rb:905:in `result'
        from /home/gencer/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/railties-6.1.3.2/lib/rails/generators/migration.rb:67:in `block in migration_template'
        from /home/gencer/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/thor-1.1.0/lib/thor/actions/create_file.rb:53:in `render'
        from /home/gencer/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/railties-6.1.3.2/lib/rails/generators/actions/create_migration.rb:19:in `identical?'
        from /home/gencer/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/railties-6.1.3.2/lib/rails/generators/actions/create_migration.rb:50:in `on_conflict_behavior'
        from /home/gencer/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/thor-1.1.0/lib/thor/actions/empty_directory.rb:115:in `invoke_with_conflict_check'
        from /home/gencer/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/thor-1.1.0/lib/thor/actions/create_file.rb:60:in `invoke!'
        from /home/gencer/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/railties-6.1.3.2/lib/rails/generators/actions/create_migration.rb:23:in `invoke!'
        from /home/gencer/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/thor-1.1.0/lib/thor/actions.rb:93:in `action'
        from /home/gencer/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/railties-6.1.3.2/lib/rails/generators/migration.rb:36:in `create_migration'
        from /home/gencer/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/railties-6.1.3.2/lib/rails/generators/migration.rb:65:in `migration_template'
        from /home/gencer/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/rodauth-rails-0.18.0/lib/generators/rodauth/migration_helpers.rb:12:in `migration_template'
        from /home/gencer/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/rodauth-rails-0.18.0/lib/generators/rodauth/install_generator.rb:33:in `create_rodauth_migration'
        from /home/gencer/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/thor-1.1.0/lib/thor/command.rb:27:in `run'
        from /home/gencer/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/thor-1.1.0/lib/thor/invocation.rb:127:in `invoke_command'
        from /home/gencer/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/thor-1.1.0/lib/thor/invocation.rb:134:in `block in invoke_all'
        from /home/gencer/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/thor-1.1.0/lib/thor/invocation.rb:134:in `each'
        from /home/gencer/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/thor-1.1.0/lib/thor/invocation.rb:134:in `map'
        from /home/gencer/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/thor-1.1.0/lib/thor/invocation.rb:134:in `invoke_all'
        from /home/gencer/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/thor-1.1.0/lib/thor/group.rb:232:in `dispatch'
        from /home/gencer/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/thor-1.1.0/lib/thor/base.rb:485:in `start'
        from /home/gencer/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/railties-6.1.3.2/lib/rails/generators.rb:275:in `invoke'
        from /home/gencer/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/railties-6.1.3.2/lib/rails/commands/generate/generate_command.rb:26:in `perform'
        from /home/gencer/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/thor-1.1.0/lib/thor/command.rb:27:in `run'
        from /home/gencer/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/thor-1.1.0/lib/thor/invocation.rb:127:in `invoke_command'
        from /home/gencer/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/thor-1.1.0/lib/thor.rb:392:in `dispatch'
        from /home/gencer/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/railties-6.1.3.2/lib/rails/command/base.rb:69:in `perform'
        from /home/gencer/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/railties-6.1.3.2/lib/rails/command.rb:50:in `invoke'
        from /home/gencer/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/railties-6.1.3.2/lib/rails/commands.rb:18:in `<top (required)>'
        from /home/gencer/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/zeitwerk-2.4.2/lib/zeitwerk/kernel.rb:34:in `require'
        from /home/gencer/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/zeitwerk-2.4.2/lib/zeitwerk/kernel.rb:34:in `require'
        from /home/gencer/sources/my-org/my-org/bin/rails:9:in `<top (required)>'
        from /home/gencer/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/activesupport-6.1.3.2/lib/active_support/fork_tracker.rb:10:in `block in fork'
        from /home/gencer/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/activesupport-6.1.3.2/lib/active_support/fork_tracker.rb:10:in `block in fork'
        from /home/gencer/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/activesupport-6.1.3.2/lib/active_support/fork_tracker.rb:8:in `fork'
        from /home/gencer/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/activesupport-6.1.3.2/lib/active_support/fork_tracker.rb:8:in `fork'
        from /home/gencer/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/activesupport-6.1.3.2/lib/active_support/fork_tracker.rb:26:in `fork'
        from /home/gencer/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/activesupport-6.1.3.2/lib/active_support/fork_tracker.rb:8:in `fork'
        from /home/gencer/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/activesupport-6.1.3.2/lib/active_support/fork_tracker.rb:26:in `fork'
        from <internal:/home/gencer/.rbenv/versions/3.0.1/lib/ruby/3.0.0/rubygems/core_ext/kernel_require.rb>:85:in `require'
        from <internal:/home/gencer/.rbenv/versions/3.0.1/lib/ruby/3.0.0/rubygems/core_ext/kernel_require.rb>:85:in `require'
        from -e:1:in `<main>'

changing email subject on password reset rquest

Hello,

I'am trying to change the email subject with

reset_password_email_subject "Passwort zurรผcksetzen"

in lib/rodauth_app.rb.

I also changed the mail template which works great, but the email subject still says "Reset password".

I would be very grateful for a tip.

Problem with Account verification with multiple configuration

Gemfile

  • ruby "3.1.2"
  • gem "rails", "~> 7.0.3"
  • gem 'rodauth-rails', '~> 1.4', '>= 1.4.2'

I have set RodauthAdmin

class RodauthApp < Rodauth::Rails::App
  # ...
  configure RodauthAdmin, :admin
  
  route do |r|
    # ...
    r.rodauth(:admin)
  end
end
class RodauthAdmin < Rodauth::Rails::Auth
  # ...
  after_create_account do
    db[:account_types].insert(account_id: account_id, type: "admin")
  end

  auth_class_eval do
    def account_ds(*)
      super.join(:account_types, account_id: :id).where(type: "admin")
    end
  end
end

and type table

class CreateAccountTypes < ActiveRecord::Migration
  def change
    create_table :account_types, id: false do |t|
      t.references :account, foreign_key: { on_delete: :cascade }, null: false
      t.string :type, null: false
    end
  end
end

After I create admin account
RodauthApp.rodauth(:admin).create_account(login: "[email protected]", password: "secret")
I got e-mail with verify link and after clicking on verification link an error occurs
Need multiple FROM tables if updating/deleting a dataset with JOINs

And in console:

09:44:16 web.1  | Started POST "/admin/verify-account" for ::1 at 2022-05-30 09:44:16 +0200
09:44:16 web.1  | Processing by RodauthApp#call as HTML
09:44:16 web.1  |   Parameters: {"authenticity_token"=>"[FILTERED]", "commit"=>"Verify Account"}
09:44:16 web.1  |   Sequel (0.2ms)  SELECT "key" FROM "account_verification_keys" WHERE ("id" = 'b7d50431-6672-4e09-99e0-0b1cfdf5bb09') LIMIT 1
09:44:16 web.1  |   โ†ณ app/misc/rodauth_app.rb:13:in `block in <class:RodauthApp>'
09:44:16 web.1  |   Sequel (0.4ms)  SELECT * FROM "accounts" INNER JOIN "account_types" ON ("account_types"."account_id" = "accounts"."id") WHERE (("id" = 'b7d50431-6672-4e09-99e0-0b1cfdf5bb09') AND ("type" = 'admin') AND ("status" = 1)) LIMIT 1
09:44:16 web.1  |   โ†ณ app/misc/rodauth_app.rb:13:in `block in <class:RodauthApp>'
09:44:16 web.1  |   TRANSACTION (0.1ms)  BEGIN
09:44:16 web.1  |   โ†ณ app/misc/rodauth_app.rb:13:in `block in <class:RodauthApp>'
09:44:16 web.1  |   TRANSACTION (0.1ms)  ROLLBACK
09:44:16 web.1  |   โ†ณ app/misc/rodauth_app.rb:13:in `block in <class:RodauthApp>'
09:44:16 web.1  | Completed 500 Internal Server Error in 10ms (ActiveRecord: 0.8ms | Allocations: 4375)
09:44:16 web.1  |
09:44:16 web.1  |
09:44:16 web.1  |
09:44:16 web.1  | Sequel::Error (Need multiple FROM tables if updating/deleting a dataset with JOINs):

If I remove auth_class_eval the verification will pass withou error

JWT usage unclear

Hello Janko,

I have trouble getting JWT to work. I have what I'd call mixed mode app where I'm using normal Rails app (not --api) with Vue frontend. Vue will communicate with a dedicated Rails API to get data.

Error

When testing user signup, I hit the following error:

Started POST "/create-account" for 172.23.0.1 at 2020-12-29 19:35:16 +0000
   (0.8ms)  SELECT "schema_migrations"."version" FROM "schema_migrations" ORDER BY "schema_migrations"."version" ASC
  
NameError (uninitialized constant RodauthApp::RodauthController
Did you mean?  RodauthMailer):
  
app/lib/rodauth_app.rb:27:in `block (2 levels) in <class:RodauthApp>'
app/lib/rodauth_app.rb:162:in `block in <class:RodauthApp>'

Request

triggered by my request (deleted cookie session from the request)

curl --request POST \
  --url http://localhost:3000/create-account \
  --header 'Content-Type: application/json' \
  --data '{
	"login": "[email protected]",
	"password": "testtest"
}'

Rodauth-Rails config

class RodauthApp < Rodauth::Rails::App
  configure json: :only do
    enable(
      :create_account,
      :verify_account,
      :verify_account_grace_period,
      :login,
      :logout,
      :reset_password,
      :change_password,
      :change_password_notify,
      :change_login,
      :verify_login_change,
      :close_account,
      :jwt
    )

    rails_controller { RodauthController }

    account_status_column :status
    account_unverified_status_value 'unverified'
    account_open_status_value 'verified'
    account_closed_status_value 'closed'

    verify_account_set_password? false

    jwt_secret "123456-example"

    require_login_confirmation? false
    require_password_confirmation? false

    after_create_account do
      Profile.create!(account_id: account[:id])
    end

    after_close_account do
      Profile.find_by!(account_id: account[:id]).destroy
    end

    logout_redirect '/'

    verify_account_redirect { login_redirect }
    reset_password_redirect { login_path }
  end

  route do |r|
    r.rodauth
  end
end

Remarks

I have three remarks I feel were very nice to add to this project:

  • do not depend on Rails API mode in your generators. Make it explicit, so user can actually choose what to generate, like with rails generate rodauth:install:api. Now json: :only-dependent code (like removal of user remembering) is available only when Rails is generated with --api. I had to generate a dummy rodauth-rails project, diff my real project and dummy project just to be able to see what your generator actually added to the code. Not cool.
  • an example app leveraging such (not uncommon) "mixed mode" app where you have classic Rails app with parts enriched with Vue frontend, dedicated API controller and therefore a need for JWT would be very nice
  • how to send mails when user registers/other actions with JWT only mode. Currently mails are available only in non-JWT mode of rodauth-rails without any info how to do that with JWT. It is not clear how to do that, as (according to the Rodauth author) you have to enable render plugin. Not sure how to do that with your DSL:

Note that by default, the features that send email depend on the render plugin, so if using the :json=>:only option, you either need to load the render plugin manually or you need to use the necessary *_email_body configuration options to specify the body of the emails.

Issue when requiring authentication following README guidelines

Readme is mentionning that authentication can be achieved at Rails route level with the following code:

# config/routes.rb
Rails.application.routes.draw do
  constraints -> (r) { r.env["rodauth"].require_authentication } do
    namespace :admin do
      # ...
    end
  end
end

This code is working when the user is not logged (accessing this route will redirect to the login page), but once logged in, the route can no be accessed.

r.env["rodauth"].require_authentication returns nil when the user is authenticated so the constraints does not match thus the route can not be accessed.

I've created a branch with a failing test: nicolas-besnard@53e3af6

Rodauth 2.0 dependency?

Hi!

This looks super interesting. We already have a cobbled together rails-roda implementation and I'm keen to try this, but the spec.add_dependency "rodauth", "~> 2.0" makes me think I'm missing something!

Is that deliberate?

Simple install doesn't work : undefined method `login_path'

Sorry to open the 43rd issue, 42 was perfect ;)

Step to reproduce. MacOS 10.15.7 :

$> ruby -v  
ruby 3.0.0p0  
$> bundle -v  
Bundler version 2.2.11  
$> npm -v  
8.3.0 
$> yarn -v  
1.22.10
$> psql --version  
psql (PostgreSQL) 13.1 

Rails 7 will be used

  mkdir rodemo && cd rodemo  
  echo "source 'https://rubygems.org'" > Gemfile  
  echo "gem 'rails', '7.0.0'" >> Gemfile  
  bundle install  
  bundle exec rails new . --force -d=postgresql  
  bundle update

  # Create a default controller
  echo "class HomeController < ApplicationController" > app/controllers/home_controller.rb
  echo "end" >> app/controllers/home_controller.rb

  # Create routes
  echo "Rails.application.routes.draw do" > config/routes.rb
  echo '  get "home/index"' >> config/routes.rb
  echo '  root to: "home#index"' >> config/routes.rb
  echo 'end' >> config/routes.rb

  # Create a default view
  mkdir app/views/home
  echo '<h1>This is home</h1>' > app/views/home/index.html.erb
  
  # Create database and schema.rb
  bin/rails db:create
  bin/rails db:migrate

Now let's add rodauth-rails to this simple, default Rails app :

inside Gemfile

gem "rodauth-rails"

then bundle install

then bin/rails generate rodauth:install

then bin/rails db:migrate

then bin/rails rodauth:routes --trace

Will produce this error :

** Invoke rodauth:routes (first_time)
** Invoke environment (first_time)
** Execute environment
** Execute rodauth:routes
Routes handled by RodauthApp:
rails aborted!
NoMethodError: undefined method login_path' for #<Class:0x00007f8e7f08d850> /Users/david/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/rodauth-rails-0.18.1/lib/rodauth/rails/tasks.rake:14:in public_send'
/Users/david/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/rodauth-rails-0.18.1/lib/rodauth/rails/tasks.rake:14:in block (4 levels) in <main>' /Users/david/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/rodauth-rails-0.18.1/lib/rodauth/rails/tasks.rake:10:in map'
/Users/david/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/rodauth-rails-0.18.1/lib/rodauth/rails/tasks.rake:10:in block (3 levels) in <main>' /Users/david/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/rodauth-rails-0.18.1/lib/rodauth/rails/tasks.rake:7:in each'
/Users/david/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/rodauth-rails-0.18.1/lib/rodauth/rails/tasks.rake:7:in block (2 levels) in <main>' /Users/david/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/rake-13.0.6/lib/rake/task.rb:281:in block in execute'
/Users/david/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/rake-13.0.6/lib/rake/task.rb:281:in each' /Users/david/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/rake-13.0.6/lib/rake/task.rb:281:in execute'
/Users/david/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/rake-13.0.6/lib/rake/task.rb:219:in block in invoke_with_call_chain' /Users/david/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/rake-13.0.6/lib/rake/task.rb:199:in synchronize'
/Users/david/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/rake-13.0.6/lib/rake/task.rb:199:in invoke_with_call_chain' /Users/david/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/rake-13.0.6/lib/rake/task.rb:188:in invoke'
/Users/david/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/rake-13.0.6/lib/rake/application.rb:160:in invoke_task' /Users/david/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/rake-13.0.6/lib/rake/application.rb:116:in block (2 levels) in top_level'
/Users/david/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/rake-13.0.6/lib/rake/application.rb:116:in each' /Users/david/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/rake-13.0.6/lib/rake/application.rb:116:in block in top_level'
/Users/david/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/rake-13.0.6/lib/rake/application.rb:125:in run_with_threads' /Users/david/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/rake-13.0.6/lib/rake/application.rb:110:in top_level'
/Users/david/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/railties-7.0.0/lib/rails/commands/rake/rake_command.rb:24:in block (2 levels) in perform' /Users/david/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/rake-13.0.6/lib/rake/application.rb:186:in standard_exception_handling'
/Users/david/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/railties-7.0.0/lib/rails/commands/rake/rake_command.rb:24:in block in perform' /Users/david/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/rake-13.0.6/lib/rake/rake_module.rb:59:in with_application'
/Users/david/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/railties-7.0.0/lib/rails/commands/rake/rake_command.rb:18:in perform' /Users/david/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/railties-7.0.0/lib/rails/command.rb:51:in invoke'
/Users/david/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/railties-7.0.0/lib/rails/commands.rb:18:in <main>' /Users/david/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/bootsnap-1.9.3/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:23:in require'
/Users/david/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/bootsnap-1.9.3/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:23:in block in require_with_bootsnap_lfi' /Users/david/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/bootsnap-1.9.3/lib/bootsnap/load_path_cache/loaded_features_index.rb:100:in register'
/Users/david/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/bootsnap-1.9.3/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:22:in require_with_bootsnap_lfi' /Users/david/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/bootsnap-1.9.3/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:31:in require'
bin/rails:4:in `

'
Tasks: TOP => rodauth:routes

Is it ready for a rails api only app?

Howdy! ๐Ÿค 

First of all, thanks for putting this gem to make it easier to us all. Happy to collaborate to it. I am giving it a try on this gem to add Rodauth to my API only project, but I am facing some problems.

migrations don't run

The first one was regarding migrations. To run the migration I had to comment all the lines inside both ./config/initializers/rodauth.rb and ./config/initializers/sequel.rb. I was getting a connection error every time I tried to run migrations.

Another thing I noticed is that the generated ./config/initializers/sequel.rb looks like this:

require "sequel/core"

# initialize Sequel and have it reuse Active Record's database connection
DB = Sequel.postgres(extensions: :activerecord_connection)

And the version available at your example repo is slightly different:

require "sequel/core"

# initialize Sequel and have it reuse Active Record's database connection
DB = Sequel.postgres(extensions: :activerecord_connection)
DB.extension :activerecord_connection

RodauthApp could not be found

Later I had to deal with a NameError (uninitialized constant RodauthApp) error that I could solve moving the ./lib folder into ./app/lib.

alien method to an API only rails app being invoked

Now I am dealing with somewhere in the lib trying to use flash on an API only app: NoMethodError (undefined method 'flash' for # <ActionDispatch::Request:0x000055bce3a49e08>)

I am not sure if this is enough to let the gem know that we want the REST API only.

class RodauthApp < Rodauth::Rails::App
  configure :json => :only do
  # lots of stuff
end

the relevant gems on my project

ruby '2.7.0'
gem 'rails', '~> 6.0', '>= 6.0.2.2'
gem 'pg', '>= 0.18', '< 2.0'
gem 'rodauth-rails', '~> 0.3'

Any help is welcome. Thanks!

`config.action_mailer.default_url_options` is being ignored when setting token_link in verify_account email

Hi,

I've got a Rails app in API mode.
When I set a config.action_mailer.default_url_options in Rails environment file โ€” it's being ignored during email generation, specifically when app generates a verify account email with a verification token link.

Example:

# config/environments/development.rb
# app runs on localhost:3000 

...
config.action_mailer.default_url_options = { host: '127.0.0.1', port: '8080' }
...

and in account verification email I still get a http://localhost:3000/verify-account?key=a8b59ebd-bc3e-4f53-9dbb-52283ae328ce_p6wyObT2QMUvdKG0ExhUmnbdvhPPaM9FZLtwX_i9WGY

Is this an expected behavior for API-only app?

Thank you.

Hotwire Turbo Support

Hi, is this gem support hotwire turbo(turbo-rails) which requires to replace ujs and turbolinks?

[ignore: user error] ActionController::InvalidAuthenticityToken after loading session from memory (`remember` feature)

It appears that session[:_csrf_token] is not set in the session in this case. This causes it to be lazily set on each check of the submitted token but because it is generated after the token used in the form, it does not match.

On a page load after session restore (i.e. delete the session cookie, but leave the remember cookie):

>> session.to_h
=> {"account_id"=>"9dd41226-d110-4b13-b6de-c8d6b09d441f", "active_session_id"=>"XSrfiUyX_MYIatmN1HyJMglgwFHOQOUR0EmWfi14fY4", "authenticated_by"=>["remember"], "two_factor_auth_setup"=>false

I have confirmed that session[:_csrf_token] is set after the failed token (on the server side only, though) and is different on every occurrence.

rails_controller_instance.send(:real_csrf_token, session) is also different between requests, because the real token is not being stored in the session.

inconsistencies for rodauth.authenticated_by between controllers and RoadauthApp

This might be a Rodauth bug rather than a rodauth-rails one.

I've configured my RoadauthApp to use OTP.

class RodauthApp < Rodauth::Rails::App
  configure(json: true) do
    # List of authentication features that are loaded.
    enable :create_account, :verify_account, :verify_account_grace_period,
      :login, :remember, :logout,
      :close_account, :two_factor_base, :otp

  route do |r|
    rodauth.load_memory # autologin remembered users
    r.rodauth # route rodauth requests

    Rails.logger.info '  -- rodauth.authenticated_by'
    Rails.logger.info rodauth.authenticated_by.inspect
  end
end

When logging in, and entering OTP code:

  • In controller: rodauth.authenticated_by # => ["password", "totp"]
  • In RodauthApp: rodauth.authenticated_by # => ["password", "totp"]

So far, so good.

Now, I'm changing the "remember settings" by visiting /remember. When clicking "Remember me" then "Change Remember Setting", I still have the same output in the controller and in RoadauthApp (rodauth.authenticated_by # => ["password", "totp"])

However, after modifying the remember setting and trying to access a protected resource:

  • In controller: rodauth.authenticated_by # => ["password", "totp"]
  • In RodauthApp: rodauth.authenticated_by # => ["remember", "totp"]

I'm not sure this is a bug, but the behavior seems weird.

I'll be happy to give you an app to reproduce this

undefined method flash=

I'm trying to use rodauth-rails in a rails api app. I've noticed a couple things about generating the rodauth install using the --json flag.

If I specify the generator to use --json it still enables :jwt in the rodauth_app file and includes a jwt_secret. I then get errors because it's expecting the jwt gem to be installed, which I'm not needing. The documentation makes it sound like I should only be getting all the jwt configurations if I specified --jwt with the generator.

Once I remove the jwt_secret and the enable :jwt, I stop getting jwt errors and start getting undefined method flash=' for #<ActionDispatch::Request:0x00007fabaea1fd90> when I try to post to /login. Since this is an api, I don't have a flash, and from reading through previous issues it sounds like this was fixed in version 0.4. I'm on 0.17.

This is on a fresh rails 6.1.4.1 api app. The only changes I have made are adding rack-cors, enabling cookies and session cookies in application.rb, and adding include ActionController::RequestForgeryProtection with protect_from_forgery with: :null_session in the application controller.

On a side note, how can I inspect a request being sent to a rodauth route? I can't find a place to put a byebug call. My client is sending an X-CSRF-Token header with a good value, but I'm still getting Can't verify CSRF token authenticity. so I'm needing to troubleshoot that further, but that's not your problem. :-)

Thank you!

#set_jwt_token not working

Hi there,

I have a :user configuration with jwt enabled.

When I call rodauth(:user).set_jwt_token(rodauth(:user).jwt_token) the Authentication header doesn't get set. So instead, I set it manually, like this: response.headers['Authorization'] = rodauth(:user).jwt_token.

I looked up the documentation for this method here - https://rodauth.jeremyevans.net/rdoc/index.html
It's exactly what I'm doing manually. Here's the source:

# File lib/rodauth/features/jwt.rb
def set_jwt_token(token)
  response.headers['Authorization'] = token
end

Here's the context that I'm using it in, in app/controllers/application_controller.rb:

def current_account
  unless @current_account
    if rodauth(:user).use_jwt?
      if rodauth(:user).valid_jwt?
        @current_account = UserAccount.find(rodauth(:user).session_value)
        response.headers['Authorization'] = rodauth(:user).jwt_token
      end
    else
      @current_account = AdminAccount.find(rodauth(:admin).session_value)
    end
  end
rescue ActiveRecord::RecordNotFound
  rodauth.logout
  rodauth.login_required 
end

Perhaps I'm doing something wrong. I thought I'd report it in case there's a bug.

Thanks!

Change the model name

Is there a way to rename the model to User instead of Account?
I'm building a banking app that has a table called accounts to store users' bank account details. I don't want to change the table name to something else, because it can be misleading to developers.

Slow `ActionView::PathResolver#find_template_paths`

I'm not sure if there is a clear fix that will work for all cases, but I wanted to bring this to your attention.

We've have really slow view loading in certain contexts. The glob that it searches for views ends up compiling to:

app/views/rodauth/login{.en,}{.html,.text,.js,.css,.ics,.csv,.vcf,.vtt,.png,.jpeg,.gif,.bmp,.tiff,.svg,.mpeg,.mp3,.ogg,.m4a,.webm,.mp4,.otf,.ttf,.woff,.woff2,.xml,.rss,.atom,.yaml,.multipart_form,.url_encoded_form,.json,.pdf,.zip,.gzip,}{}{.raw,.erb,.html,.builder,.ruby,.coffee,.slim,.jbuilder,.haml,}

I'm supposing we don't have this problem elsewhere because Rails might eliminate options here from information from routing layer, but that's just speculation.

I think somehow incorporating respond_to { html { } } around the renders may cut this down significantly.

For some members of our team (particularly macOS users where Docker's filesystem access is pretty indirect), these renders can be > 300ms.

We are on Rails 5, so perhaps Rails 6 wouldn't have this problem due to rails/rails#33860

We've worked around this by inserting the following into our rodauth controller:

  def _render_template(options)
    variant = options.delete(:variant)
    assigns = options.delete(:assigns)
    context = view_context

    context.assign assigns if assigns
    lookup_context.rendered_format = nil if options[:formats]
    lookup_context.variants = variant if variant

    lookup_context.formats = [:html]
    lookup_context.handlers = [:slim, :erb]

    view_renderer.render(context, options)
  end

JWT in the response body

@janko , Just one doubt!
after setup the jwt config
create the account and login the auth_token goes to headers, where should get by response body

r-header

So, i would like to know how to get jwt in the response body.

axios

PG::UndefinedColumn: ERROR: column "email" does not exist.

Hi there,

First off, thanks for making this. Quite nice to have an easy in to Rodauth as a Rails dev.

I am finally trying out Rodauth Rails and am using blind_index for the Account model. I'm running into an error that has already been mentioned here:
ankane/blind_index#10

I'm having a bit of a hard time tracking down whether or not the query being a string is true or not. I can't find any string queries, but I also don't know if I should be digging through the source code here, or in the rodauth repo itself.

Any help is appreciated, but I can post on Stack Overflow instead if needed.

Thanks!

Also: This is all the console is giving me for an error:

Sequel::DatabaseError (PG::UndefinedColumn: ERROR:  column "email" does not exist
LINE 1: SELECT * FROM "accounts" WHERE (("email" = '[email protected]') AND ...
                                         ^
):
  
app/misc/rodauth_app.rb:9:in `block in <class:RodauthApp>'

Security issue with a "default config"

After uncommenting this line (after_load_memory { two_factor_update_session("totp") if two_factor_authentication_setup? }), I realised that I can access protected resource via ajax even though I never entered the TOTP code. Trying to manually go to a protected page (by changing the URL) will redirect me to /otp-auth

After looking at the code, two_factor_authentication_setup? will return true if TOTP is configured, thus we will update the authenticated_by content even though we never really performed it.

To reproduce it, you just need to add a lit

I might have misconfigured something though

My rodauth_app.rb:

class RodauthApp < Rodauth::Rails::App
  configure(json: true) do
    # List of authentication features that are loaded.
    enable :create_account, :verify_account, :verify_account_grace_period,
      :login, :remember, :logout,
      :reset_password, :change_password, :change_password_notify,
      :change_login, :verify_login_change,
      :close_account, :two_factor_base, :otp, :recovery_codes

    after_load_memory { two_factor_update_session("totp") if two_factor_authentication_setup? }
    route do |r|
      rodauth.load_memory # autologin remembered users

      r.rodauth # route rodauth requests

      if rodauth.logged_in?
        Rails.logger.info '  -- rodauth.logged_in?'
        Rails.logger.info rodauth.logged_in?.inspect

        Rails.logger.info '  -- rodauth.two_factor_authentication_setup?'
        Rails.logger.info rodauth.two_factor_authentication_setup?.inspect

        Rails.logger.info '  -- rodauth.two_factor_authenticated?'
        Rails.logger.info (rodauth.two_factor_authenticated?).inspect

        Rails.logger.info '  -- (rodauth.logged_in? && rodauth.two_factor_authentication_setup? && !rodauth.two_factor_authenticated?)'
        Rails.logger.info (rodauth.logged_in? && rodauth.two_factor_authentication_setup? && !rodauth.two_factor_authenticated?).inspect

        Rails.logger.info '  -- rodauth.authenticated_by'
        Rails.logger.info rodauth.authenticated_by.inspect
      end

      if rodauth.logged_in? && rodauth.two_factor_authentication_setup? && !rodauth.two_factor_authenticated?
        rodauth.require_two_factor_authenticated
      end
    end
  end
end

RodAuth Route Prefix Ignored in Rails

It seems when configuring the RodAuth route prefix with prefix "/auth", it's not working. I added the prefix to rodauth_app.rb, and the routes then appear to be correct when running rails rodauth:routes, but performing a request to /auth/login I get No route matches "/auth/login". POSTing to "/login" does work, even though the rodauth routes list /auth/**. Restarting the server doesn't make a difference. Do I need to do something different to change where the rodauth routes are mounted?

Improve documentation to ease users' transition to this gem

Would you accept a PR to:

  • Improve the rodauth:migrations to add a new parameter (--account-name?) to easily create new model / migration when using rodauth with multi configuration
  • Add documentation on how to create new account programmatically
  • Add documentation about the new RodauthController to create when using multiple accounts

Documentation on rodauth internal_request is confusing

Hi,

I just started using rodauth and rodauth_rails. I wanted to start using the internal_request feature and had a lot of issues getting it to work.

First, the documentation shows that Rodauth::Rails.rodauth should be used to make internal calls. However I figured out (from the tests) that it's actually RodauthApp.rodauth that should be used.

Also the usage of :env and alike is poorly explained. Again, according to the tests these should be added to the final call, like so: RodauthApp.rodauth.create_account(login: "[email protected]", password: "secret", env: env) while the docs make it look like it should be Rodauth::Rails.rodauth(env: env).create_account(login: "[email protected]", password: "secret").

Not sure if I just "read i wrong" but its confusing for sure.

Added routes are not showing on "rails routes"

I just added the gem to a newly created app.

When I go to /login, I see the login page.

When I cmd rails routes, I don't see the login route. I randomly tried rodauth.login_path and it works. Is there a way to have a list of all the added routes?

Translating the flash method on Rodauth::InternalRequestError

In the app I am currently building, I have admins, who can create users (called employees in the app). They do this through an internal request. When someone tries to create a user with an already used email, a Rodauth::InternalRequestError is raised. I want to use this error to show a flash message explaining what happened. Rodauth::InternalRequestError has a flash method, but I cannot figure out how to translate it in a good way (or if this is bad practice).
When I inspect the error it looks like this:

#<Rodauth::InternalRequestError: translation missing: da.rodauth.create_account_error_flash (already_an_account_with_this_login, {"login"=>"invalid login, translation missing: da.rodauth.already_an_account_with_this_login_message"})>

This looked promising, but I couldn't find a way to interpolate any values in the da.rodauth.create_account_error_flash translation. Are there any keys for this or should I find another way to do this?

Thanks!

Handling File Uploads During Account Creation

I'm creating a sign up page for a new application. One of the fields I'd like to add is a profile picture. I'm using Active Storage to handle the upload, so I modified the create-account.html.erb template to this:

<%= form_tag rodauth.create_account_path, method: :post, multipart: true do %>

  ...

  <div class="form-group">
    <%= label_tag :profile_picture %>
    <%= file_field_tag :profile_picture, value: params["profile_picture"] %>
  </div>

  <%= render "submit", value: "Create Account" %>
<% end %>

On the back-end, I'm using the after_create_account hook to create a Profile record where I intend to store the image.

    after_create_account do
      Profile.create!(
		...
		picture: param("profile_picture"),
      )
    end

But instead of getting a file object of some kind as I would expect, param("profile_picture") is a string that looks like this:

"{:filename=>\"profile-latest.jpg\", :type=>\"image/jpeg\", :name=>\"profile_picture\", :tempfile=>#<Tempfile:/var/folders/sc/2vr8_7k56pz3hr0grcmpkxt00000gn/T/RackMultipart20210529-54743-ib4e4r.jpg>, :head=>\"Content-Disposition: form-data; name=\\\"profile_picture\\\"; filename=\\\"profile-latest.jpg\\\"\\r\\nContent-Type: image/jpeg\\r\\n\"}"

The Profile.create! code above doesn't work, obviously, and instead throws an ActiveSupport::MessageVerifier::InvalidSignature exception.

I tried looking through the rodauth-rails code to determine what's happening, but I failed to figure it out. Is what I've described above the expected behavior?

Integrate with Rails `rescue_from` to support custom 5xx error pages

Hey Janko,

Do you have any advice on how to re-use the existing styled 5xx pages on the Rails' side for exceptions which happen in Rodauth land? Is this something rodauth-rails could/should cover in its integration?

I wonder if the around_rodauth we discussed in #2 (comment) might be necessary or even if that would simply work if process_actions is also responsible for Rails' rescue_from behaviour (I haven't checked).

undefined method `create_database_authentication_functions' for Rodauth:Module

Hi,

I just tried rodauth-rails 0.7 with the a clean install of rails 6.1 and followed the setup instructions but ran into this error when enabling use_database_authentication_functions? true, I even tried adding require 'rodauth' to the migration file:

undefined method `create_database_authentication_functions' for Rodauth:Module
/app-template/db/migrate/20201210212807_create_rodauth_database_functions.rb:5:in `up'

undefined method `allow_forgery_protection='

Hello mate,

I am trying your standard configuration on my rails-api app, and I am getting this error below.

My code is running on [email protected], [email protected] and [email protected].

NoMethodError (undefined method `allow_forgery_protection=' for #<RodauthController:0x000000000066d0>):
  
app/lib/rodauth_app.rb:146:in `block in <class:RodauthApp>'

It seems that there is something here that is triggering the problem

class RodauthApp < Rodauth::Rails::App
  configure json: :only do
    # List of authentication features that are loaded.
    enable :create_account, :verify_account, :verify_account_grace_period,
      :login, :logout, :jwt,
      :reset_password, :change_password, :change_password_notify,
      :change_login, :verify_login_change,
      :close_account

  # lots of stuff

  route do |r|
    # the line below is the 146
    r.rodauth # route rodauth requests
  end

  # more stuff
end

DB is not rodauth's db

I'm having a bit of inconsistency in my tests, and I think it's due to this: https://github.com/janko/rodauth-rails/blob/master/lib/generators/rodauth/templates/config/initializers/sequel.rb

In this example, DB is the sequel connection loading the activerecord connection. However, this might not be the same as the rodauth db, because rodauth defines db as Sequel::DATABASES.first, and that might not be DB. This config thereby assumes there's only one sequel db loaded, and that might not be the case (I'm testing roda and rails integrations).

As a change proposal, I'd suggest maybe defining the db constant in rodauth app:

class RodauthApp < Rodauth::Rails::App

  configure do
    enable :login, :http_basic_auth, :oauth

    db DB
    rails_controller { RodauthController }
...

Alternatively, one could define the DB directly in the rodauth app file, unless there's real need to expose the DB constant in initializer code.

Handling Admin User

In devise when I want to have admin users, I create a separate model AdminUser.
How do I handle that here?

turbo throws error when authenticating "view recovery codes"

I finally got around to fiddle with this gem. Super convenient setup, great documentation.

I have implemented OTPs and Recovery Codes thanks to your blog article.

It's a fresh install: Rails 6.1.4.1 and Ruby 3.0.2. I have also added the latest turbo-rails.

When accessing /recovery-codes I get presented with a form to enter my password (two_factor_modifications_require_password? is true)

When submitting the form, turbo throws the following JavaScript error: Error: Form responses must redirect to another location.

turbo can't render the page since the location hasn't changed.
I can work around it by disabling turbo for this particular form by adding data: { turbo: false } to the form_tag in _recovery_codes_form.html.erb

<!--_recovery_codes_form.html.erb-->
<%= form_tag rodauth.recovery_codes_path, method: :post, data: { turbo: false } do %>
...

The form submission then works as expected.

I wonder if there's anotherโ€”more elegantโ€”way to solve this. Maybe by passing some kind of setting in the class RodauthApp < Rodauth::Rails::App so it would work with turbo enabled.
recovery_codes_route won't work since it's overriding the path for both, the form and the redirect.

Thank you!

Database connection is still failing when creating a database

Thank you for providing a fix so quickly.

๐ŸŽ‰ The invocation of flash method is fixed!

Unfortunately, the database problem still happening. I discovered that it only happens when I try to create the database. I created a repo with the vanilla rails API app I used and my docker setup. I hope it helps.

The steps:

  • Try to run rake db:create -- it fails (see trace below)
  • Comment all the lines in config/initializers/sequel.rb
  • Try again to run rake db:create -- it succeeds
  • Uncomment all the lines in config/initializers/sequel.rb
  • Try running rake db:migrate -- it succeeds
  • curl localhost:3000 and it works

I have the following questions:

  • Shouldn't the rodauth routes to be present when I run rails routes? I only got the default Rails routes.
  • Do an API only app require the app/controllers/rodauth_controller.rb file?

Here is the trace for the failed rake db:create. It feels like some other component tries to reach the database before its creation, and then fails because it doesn't exist.

rake aborted!
ActiveRecord::NoDatabaseError: FATAL:  database "api_development" does not exist
/usr/local/bundle/gems/activerecord-6.0.3.4/lib/active_record/connection_adapters/postgresql_adapter.rb:50:in `rescue in postgresql_connection'
/usr/local/bundle/gems/activerecord-6.0.3.4/lib/active_record/connection_adapters/postgresql_adapter.rb:33:in `postgresql_connection'
/usr/local/bundle/gems/activerecord-6.0.3.4/lib/active_record/connection_adapters/abstract/connection_pool.rb:887:in `new_connection'
/usr/local/bundle/gems/activerecord-6.0.3.4/lib/active_record/connection_adapters/abstract/connection_pool.rb:931:in `checkout_new_connection'
/usr/local/bundle/gems/activerecord-6.0.3.4/lib/active_record/connection_adapters/abstract/connection_pool.rb:910:in `try_to_checkout_new_connection'
/usr/local/bundle/gems/activerecord-6.0.3.4/lib/active_record/connection_adapters/abstract/connection_pool.rb:871:in `acquire_connection'
/usr/local/bundle/gems/activerecord-6.0.3.4/lib/active_record/connection_adapters/abstract/connection_pool.rb:593:in `checkout'
/usr/local/bundle/gems/activerecord-6.0.3.4/lib/active_record/connection_adapters/abstract/connection_pool.rb:437:in `connection'
/usr/local/bundle/gems/activerecord-6.0.3.4/lib/active_record/connection_adapters/abstract/connection_pool.rb:1119:in `retrieve_connection'
/usr/local/bundle/gems/activerecord-6.0.3.4/lib/active_record/connection_handling.rb:221:in `retrieve_connection'
/usr/local/bundle/gems/activerecord-6.0.3.4/lib/active_record/connection_handling.rb:189:in `connection'
/usr/local/bundle/gems/sequel-activerecord_connection-1.0.1/lib/sequel/extensions/activerecord_connection.rb:130:in `activerecord_connection'
/usr/local/bundle/gems/sequel-activerecord_connection-1.0.1/lib/sequel/extensions/activerecord_connection.rb:122:in `activerecord_lock'
/usr/local/bundle/gems/sequel-activerecord_connection-1.0.1/lib/sequel/extensions/activerecord_connection.rb:34:in `synchronize'
/usr/local/bundle/gems/sequel-activerecord_connection-1.0.1/lib/sequel/extensions/activerecord_connection/postgres.rb:5:in `synchronize'
/usr/local/bundle/gems/sequel-5.38.0/lib/sequel/database/connecting.rb:278:in `test_connection'
/usr/local/bundle/gems/sequel-5.38.0/lib/sequel/database/misc.rb:169:in `initialize'
/usr/local/bundle/gems/sequel-5.38.0/lib/sequel/database/connecting.rb:57:in `new'
/usr/local/bundle/gems/sequel-5.38.0/lib/sequel/database/connecting.rb:57:in `connect'
/usr/local/bundle/gems/sequel-5.38.0/lib/sequel/core.rb:124:in `connect'
/usr/local/bundle/gems/sequel-5.38.0/lib/sequel/core.rb:402:in `adapter_method'
/usr/local/bundle/gems/sequel-5.38.0/lib/sequel/core.rb:409:in `block (2 levels) in def_adapter_method'
/api/config/initializers/sequel.rb:4:in `<main>'
/usr/local/bundle/gems/bootsnap-1.5.0/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:59:in `load'
/usr/local/bundle/gems/bootsnap-1.5.0/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:59:in `load'
/usr/local/bundle/gems/activesupport-6.0.3.4/lib/active_support/dependencies.rb:318:in `block in load'
/usr/local/bundle/gems/activesupport-6.0.3.4/lib/active_support/dependencies.rb:291:in `load_dependency'
/usr/local/bundle/gems/activesupport-6.0.3.4/lib/active_support/dependencies.rb:318:in `load'
/usr/local/bundle/gems/railties-6.0.3.4/lib/rails/engine.rb:666:in `block in load_config_initializer'
/usr/local/bundle/gems/activesupport-6.0.3.4/lib/active_support/notifications.rb:182:in `instrument'
/usr/local/bundle/gems/railties-6.0.3.4/lib/rails/engine.rb:665:in `load_config_initializer'
/usr/local/bundle/gems/railties-6.0.3.4/lib/rails/engine.rb:625:in `block (2 levels) in <class:Engine>'
/usr/local/bundle/gems/railties-6.0.3.4/lib/rails/engine.rb:624:in `each'
/usr/local/bundle/gems/railties-6.0.3.4/lib/rails/engine.rb:624:in `block in <class:Engine>'
/usr/local/bundle/gems/railties-6.0.3.4/lib/rails/initializable.rb:32:in `instance_exec'
/usr/local/bundle/gems/railties-6.0.3.4/lib/rails/initializable.rb:32:in `run'
/usr/local/bundle/gems/railties-6.0.3.4/lib/rails/initializable.rb:61:in `block in run_initializers'
/usr/local/bundle/gems/railties-6.0.3.4/lib/rails/initializable.rb:50:in `each'
/usr/local/bundle/gems/railties-6.0.3.4/lib/rails/initializable.rb:50:in `tsort_each_child'
/usr/local/bundle/gems/railties-6.0.3.4/lib/rails/initializable.rb:60:in `run_initializers'
/usr/local/bundle/gems/railties-6.0.3.4/lib/rails/application.rb:363:in `initialize!'
/api/config/environment.rb:5:in `<main>'
/usr/local/bundle/gems/bootsnap-1.5.0/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:23:in `require'
/usr/local/bundle/gems/bootsnap-1.5.0/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:23:in `block in require_with_bootsnap_lfi'
/usr/local/bundle/gems/bootsnap-1.5.0/lib/bootsnap/load_path_cache/loaded_features_index.rb:92:in `register'
/usr/local/bundle/gems/bootsnap-1.5.0/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:22:in `require_with_bootsnap_lfi'
/usr/local/bundle/gems/bootsnap-1.5.0/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:31:in `require'
/usr/local/bundle/gems/zeitwerk-2.4.1/lib/zeitwerk/kernel.rb:33:in `require'
/usr/local/bundle/gems/activesupport-6.0.3.4/lib/active_support/dependencies.rb:324:in `block in require'
/usr/local/bundle/gems/activesupport-6.0.3.4/lib/active_support/dependencies.rb:291:in `load_dependency'
/usr/local/bundle/gems/activesupport-6.0.3.4/lib/active_support/dependencies.rb:324:in `require'
/usr/local/bundle/gems/railties-6.0.3.4/lib/rails/application.rb:339:in `require_environment!'
/usr/local/bundle/gems/railties-6.0.3.4/lib/rails/application.rb:523:in `block in run_tasks_blocks'

Caused by:
PG::ConnectionBad: FATAL:  database "api_development" does not exist
/usr/local/bundle/gems/pg-1.2.3/lib/pg.rb:58:in `initialize'
/usr/local/bundle/gems/pg-1.2.3/lib/pg.rb:58:in `new'
/usr/local/bundle/gems/pg-1.2.3/lib/pg.rb:58:in `connect'
/usr/local/bundle/gems/activerecord-6.0.3.4/lib/active_record/connection_adapters/postgresql_adapter.rb:46:in `postgresql_connection'
/usr/local/bundle/gems/activerecord-6.0.3.4/lib/active_record/connection_adapters/abstract/connection_pool.rb:887:in `new_connection'
/usr/local/bundle/gems/activerecord-6.0.3.4/lib/active_record/connection_adapters/abstract/connection_pool.rb:931:in `checkout_new_connection'
/usr/local/bundle/gems/activerecord-6.0.3.4/lib/active_record/connection_adapters/abstract/connection_pool.rb:910:in `try_to_checkout_new_connection'
/usr/local/bundle/gems/activerecord-6.0.3.4/lib/active_record/connection_adapters/abstract/connection_pool.rb:871:in `acquire_connection'
/usr/local/bundle/gems/activerecord-6.0.3.4/lib/active_record/connection_adapters/abstract/connection_pool.rb:593:in `checkout'
/usr/local/bundle/gems/activerecord-6.0.3.4/lib/active_record/connection_adapters/abstract/connection_pool.rb:437:in `connection'
/usr/local/bundle/gems/activerecord-6.0.3.4/lib/active_record/connection_adapters/abstract/connection_pool.rb:1119:in `retrieve_connection'
/usr/local/bundle/gems/activerecord-6.0.3.4/lib/active_record/connection_handling.rb:221:in `retrieve_connection'
/usr/local/bundle/gems/activerecord-6.0.3.4/lib/active_record/connection_handling.rb:189:in `connection'
/usr/local/bundle/gems/sequel-activerecord_connection-1.0.1/lib/sequel/extensions/activerecord_connection.rb:130:in `activerecord_connection'
/usr/local/bundle/gems/sequel-activerecord_connection-1.0.1/lib/sequel/extensions/activerecord_connection.rb:122:in `activerecord_lock'
/usr/local/bundle/gems/sequel-activerecord_connection-1.0.1/lib/sequel/extensions/activerecord_connection.rb:34:in `synchronize'
/usr/local/bundle/gems/sequel-activerecord_connection-1.0.1/lib/sequel/extensions/activerecord_connection/postgres.rb:5:in `synchronize'
/usr/local/bundle/gems/sequel-5.38.0/lib/sequel/database/connecting.rb:278:in `test_connection'
/usr/local/bundle/gems/sequel-5.38.0/lib/sequel/database/misc.rb:169:in `initialize'
/usr/local/bundle/gems/sequel-5.38.0/lib/sequel/database/connecting.rb:57:in `new'
/usr/local/bundle/gems/sequel-5.38.0/lib/sequel/database/connecting.rb:57:in `connect'
/usr/local/bundle/gems/sequel-5.38.0/lib/sequel/core.rb:124:in `connect'
/usr/local/bundle/gems/sequel-5.38.0/lib/sequel/core.rb:402:in `adapter_method'
/usr/local/bundle/gems/sequel-5.38.0/lib/sequel/core.rb:409:in `block (2 levels) in def_adapter_method'
/api/config/initializers/sequel.rb:4:in `<main>'
/usr/local/bundle/gems/bootsnap-1.5.0/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:59:in `load'
/usr/local/bundle/gems/bootsnap-1.5.0/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:59:in `load'
/usr/local/bundle/gems/activesupport-6.0.3.4/lib/active_support/dependencies.rb:318:in `block in load'
/usr/local/bundle/gems/activesupport-6.0.3.4/lib/active_support/dependencies.rb:291:in `load_dependency'
/usr/local/bundle/gems/activesupport-6.0.3.4/lib/active_support/dependencies.rb:318:in `load'
/usr/local/bundle/gems/railties-6.0.3.4/lib/rails/engine.rb:666:in `block in load_config_initializer'
/usr/local/bundle/gems/activesupport-6.0.3.4/lib/active_support/notifications.rb:182:in `instrument'
/usr/local/bundle/gems/railties-6.0.3.4/lib/rails/engine.rb:665:in `load_config_initializer'
/usr/local/bundle/gems/railties-6.0.3.4/lib/rails/engine.rb:625:in `block (2 levels) in <class:Engine>'
/usr/local/bundle/gems/railties-6.0.3.4/lib/rails/engine.rb:624:in `each'
/usr/local/bundle/gems/railties-6.0.3.4/lib/rails/engine.rb:624:in `block in <class:Engine>'
/usr/local/bundle/gems/railties-6.0.3.4/lib/rails/initializable.rb:32:in `instance_exec'
/usr/local/bundle/gems/railties-6.0.3.4/lib/rails/initializable.rb:32:in `run'
/usr/local/bundle/gems/railties-6.0.3.4/lib/rails/initializable.rb:61:in `block in run_initializers'
/usr/local/bundle/gems/railties-6.0.3.4/lib/rails/initializable.rb:50:in `each'
/usr/local/bundle/gems/railties-6.0.3.4/lib/rails/initializable.rb:50:in `tsort_each_child'
/usr/local/bundle/gems/railties-6.0.3.4/lib/rails/initializable.rb:60:in `run_initializers'
/usr/local/bundle/gems/railties-6.0.3.4/lib/rails/application.rb:363:in `initialize!'
/api/config/environment.rb:5:in `<main>'
/usr/local/bundle/gems/bootsnap-1.5.0/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:23:in `require'
/usr/local/bundle/gems/bootsnap-1.5.0/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:23:in `block in require_with_bootsnap_lfi'
/usr/local/bundle/gems/bootsnap-1.5.0/lib/bootsnap/load_path_cache/loaded_features_index.rb:92:in `register'
/usr/local/bundle/gems/bootsnap-1.5.0/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:22:in `require_with_bootsnap_lfi'
/usr/local/bundle/gems/bootsnap-1.5.0/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:31:in `require'
/usr/local/bundle/gems/zeitwerk-2.4.1/lib/zeitwerk/kernel.rb:33:in `require'
/usr/local/bundle/gems/activesupport-6.0.3.4/lib/active_support/dependencies.rb:324:in `block in require'
/usr/local/bundle/gems/activesupport-6.0.3.4/lib/active_support/dependencies.rb:291:in `load_dependency'
/usr/local/bundle/gems/activesupport-6.0.3.4/lib/active_support/dependencies.rb:324:in `require'
/usr/local/bundle/gems/railties-6.0.3.4/lib/rails/application.rb:339:in `require_environment!'
/usr/local/bundle/gems/railties-6.0.3.4/lib/rails/application.rb:523:in `block in run_tasks_blocks'
Tasks: TOP => db:create => db:load_config => environment
(See full trace by running task with --trace)

Originally posted by @adilsoncarvalho in #13 (comment)

Minor point of confusion in README

๐Ÿ‘‹ Hi there! This gem looks really nice (and https://github.com/janko/sequel-activerecord_connection looks like a nice way to add Sequel into existing ActiveRecord-based Rails apps)!

I was reading through the documentation in the README and I found something a bit confusing at first.

The "Multiple configurations" docs introduce an example of adding a separate :admin configuration:

rodauth-rails/README.md

Lines 513 to 547 in b85d8b3

### Multiple configurations
If you need to handle multiple types of accounts that require different
authentication logic, you can create different configurations for them:
```rb
# app/lib/rodauth_app.rb
class RodauthApp < Rodauth::Rails::App
# primary configuration
configure do
# ...
end
# alternative configuration
configure(:admin) do
# ... enable features ...
prefix "/admin"
session_key_prefix "admin_"
remember_cookie_key "_admin_remember" # if using remember feature
# ...
end
route do |r|
r.rodauth
r.on("admin") { r.rodauth(:admin) }
# ...
end
end
```
Then in your application you can reference the secondary Rodauth instance:
```rb
rodauth(:admin).login_path #=> "/admin/login"
```

However, later in the docs, the name :secondary is used for the "multiple configurations" examples:

rodauth = Rodauth::Rails.rodauth # or Rodauth::Rails.rodauth(:secondary)

request.env["rodauth.secondary"] #=> #<Rodauth::Auth> (if using multiple configurations)

rodauth(:secondary) #=> #<Rodauth::Auth> (if using multiple configurations)

<% rodauth(:secondary) #=> #<Rodauth::Auth> (if using multiple configurations) %>

configure(:secondary) { ... } # defining multiple Rodauth configurations

Ultimately, I understood this, since "secondary" is used once in the initial section:

Then in your application you can reference the secondary Rodauth instance:

but I think it would've been easier for me to follow if the later examples reused the :admin name. I've opened #26 to make that change, but let me know if you'd prefer something else.

Issue when using a roda prefix when using the same prefix as a namespace in Rails

In rails, I'm using the following routes to scope my Admins controller:

namespace :admins do
  resources :questions

  root to: 'dashboard#index'
end

Which creates the routes:

admins_root      GET  /admins(.:format)                      admins/dashboard#index
admins_questions GET  /admins/questions(.:format)            admins/questions#index

Then I'm configuration rodauth with:

configure(:admin) do
    enable :login, :logout, :remember
    rails_controller { Admins::RodauthController }
    prefix "/admins"
end

route do |r|
    rodauth.load_memory # autologin remembered users

    r.on "admins" do
      r.rodauth(:admin)
     
       if !rodauth(:admin).logged_in?
         rodauth(:admin).require_authentication
       end
    end
end

The login is working, but once I'm logged, I can not access the Rails routes and I got a blank page.

One solution would be to use an other Roda prefix (such as admin-authentication), but maybe there's a way to fallthrough Rails routes ?

I've a branch wich reproduce this issue nicolas-besnard@ddf5da4

`after_action` is not called after `rodauth.login`

If you have a around_action or after_action configured in your RodauthController, and calling rodauth.login in your action, then the processing stops and the callbacks are not called

  around_action :around_test
  after_action { puts "After action" }

  def around_test
    logger.info "Before around"
    yield
    logger.info "After around"
  end

  def test
    rodauth.account_from_login("[email protected]")
    rodauth.login("omniauth")
    puts "End of action" # not clear if this would be executed or not
  end

Output:

Started POST "/auth/test" for 127.0.0.1 at 2021-04-29 21:55:53 +0200
Processing by RodauthController#test as */*
  Parameters: {"rodauth"=>{}}
Before around
Completed   in 178ms (ActiveRecord: 69.1ms | Allocations: 62204)

Roda views and how to edit them

Hi and thanks for a great gem.

I am currently using this gem to implement a couple of different user types in an app, where account creation is done entirely in the back end. In my accounts table, I have a couple of extra fields (phone and name).

I currently have this implementation, but it both costs an extra query and comes with a risky race condition.

  def create
    RodauthApp.rodauth(:admin).create_account(rodauth_admin_params)
    Admin.last.update(admin_params.except(:email))

    redirect_to admins_path
  end

In the rodauth documentation I found this guide explaining how to send extra params during account creation: http://rodauth.jeremyevans.net/rdoc/files/doc/guides/registration_field_rdoc.html#top
Specifically this seems to be the solution to my problem:

You need to override the create-account template, which by default in Rodauth you can do by adding a create-account.erb template in your Roda views directory.

But I cannot figure out where to do this in rodauth-rails.

The configuration for my admin account looks like this:

  # ==> Secondary configurations
  configure(:admin) do
    enable :create_account, :verify_account, :verify_account_grace_period,
    :login, :logout, :remember,
    :reset_password, :change_password, :change_password_notify,
    :change_login, :verify_login_change,
    :close_account, :internal_request

    account_status_column :status
    account_unverified_status_value "unverified"
    account_open_status_value "verified"
    account_closed_status_value "closed"

    # Set password when creating account instead of when verifying.
    verify_account_set_password? true

    before_create_account do
      throw_error_status(422, "name", "must be present") if param("name").empty?
      throw_error_status(422, "phone", "must be present") if param("phone").empty?
      account[:name] = param("name")
      account[:phone] = param("phone")
      account[:type] = "Admin"
    end

    methods.grep(/_table$/) do |table_method|
      public_send(table_method) { super().to_s.sub("account", "user").to_sym }
    end
  end

rails generate rodauth:install hanging with Postgres

I'm using Rails 6.1.4.1 and Ruby 2.6.3. On a fresh rails app using the Postgres adapter, I'm not able to run the rodauth install command. It just hangs. I was able to run it using Sqlite3.

Here are the steps I perform.
rails new app-name --api -d postgresql (or non-api, didn't make a difference)
Add gem "rodauth-rails", "~> 0.17" to Gemfile
bundle install
rails generate rodauth:install --json (not using --json didn't help either)
This is where it hangs. I eventually have to kill the process.

Thanks for any help!

Do not verify CSRF for every rodauth request

I'm having a problem integrating roda-oauth in a particular case where I need to allow a POST request to an /oauth-token URI; such requests are usually performed in the backend, and do not go through an html form, therefore the CSRF protection doesn't make sense.

This route is defined from inside the rodauth feature, however, rodauth-rails runs the verify_authenticity_token by default for every rodauth request, so it breaks my flow.

My suggestion would be to remove this line, or skip it given some circumstance around the request. The roda integration with csrf protection works, so there's something being done there that allows csrf protection to be skipped which hasn't been replicated in rodauth-rails.

Facebook / Third-Party Auth Documentation or Example

Hi! I've been working on migrating our medium-sized app from Devise to Rodauth and it has been going really well so far. However, I have had some trouble finding a guide or example of facebook or other third-party auth providers being used with rodauth-rails. Is there any way we could add something to the README or include something in the demo app maybe?

Wish I could help contribute this but I've been struggling a bit to understand the process of adding facebook authentication, and the only information on it I've been able to find it is the short example in the (rodauth readme)[https://github.com/jeremyevans/rodauth] which is a little unclear to me.

Thanks! Let me know if there's anything I can do to help with this.

Disable User Creation

I would like to know how I can disable Account Creation.

I need this because I have one admin account that could create accounts.

Removing :create_account from the enable in rodauth_main.rb doen't seem to work...

Should Default Account Status Be 'Unverified'?

Hello! First time issue submitter, so please forgive me if I've done something wrong ๐Ÿ˜…

I believe the default account status in the base migration generator should be 'unverified' instead of 'verified'.

In Rodauth's default DB config, the default is 1 which maps to 'unverified': http://rodauth.jeremyevans.net/rdoc/files/README_rdoc.html#label-Creating+tables

And in the docs on this gem in the section about moving the account statuses to their own table, it also recommends setting the default to 1 which maps to 'unverified': https://github.com/janko/rodauth-rails#account-statuses

Here's the line where I think the default needs to be changed to 'unverified':

t.string :status, null: false, default: "verified"

If you'd like me to submit a PR for it I can

Can't verify CSRF token authenticity when make a post request with body using request.js

Hi,

I am trying to make a post request using request.js, the rails library for making requests through javascript injecting headers automatically to make checks like CSRF. When including parameters in the body of the post request, Rails shows a big error saying Can't verify CSRF token authenticity. I reported this error in request.js and then was asked for an example repository. Building it from 0, I noticed that the error does not occur, so I copied my rodauth configuration and was able to reproduce the error again, so I suspect this is where the problem lies.

This is the example repository and this is the originally reported issue, in case it is a request.js problem.

I would appreciate any help.

Thanks!

append_info_to_payload

@janko thank you very much for this gem, but after setup a simple api, and tried to create a account with react in the logs shows

NoMethodError (undefined method `append_info_to_payload' for #RodauthController:0x00007f3a88a094b0
17:55:43 api.1 | Did you mean? append_view_path):


  signUp() {
    fetch("/create-account", {
      method: "POST",
      headers: {
        Accept: "application/json",
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        data: {
          //username: this.state.username,
          login: this.state.login,
          password: this.state.password,
        },
      }),
    })

ArgumentError on change_login submit when new email address is the same as current

Hello Janko,

Getting this error after submitting the change_login form. The email address is the same as the currently used one.
When using a different email address, it redirects to root and sends out the email to verify the change.
Generating the views resolves the issue, though. Details below.

ArgumentError at /change-login
There was no default layout for RodauthController in #<ActionView::PathSet:0x00007f832b31aed8 @paths=[#<ActionView::OptimizedFileSystemResolver:0x00007f8329b33720 @pattern=":prefix/:action{.:locale,}{.:formats,}{+:variants,}{.:handlers,}", @unbound_templates=#<Concurrent::Map:0x00007f8329b336d0 entries=4 default_proc=nil>, @path_parser=#<ActionView::Resolver::PathParser:0x00007f832c8a9268 @regex=/
          \A
          (?:(?<prefix>.*)\/)?
          (?<partial>_)?
          (?<action>.*?)
          (?:\.(?<locale>[a-z]{2}(?:-[A-Z]{2})?))??
          (?:\.(?<format>html|text|js|css|ics|csv|vcf|vtt|png|jpeg|gif|bmp|tiff|svg|mpeg|mp3|ogg|m4a|webm|mp4|otf|ttf|woff|woff2|xml|rss|atom|yaml|multipart_form|url_encoded_form|json|pdf|zip|gzip|turbo_stream))??
          (?:\+(?<variant>[^.]*))??
          (?:\.(?<handler>raw|erb|html|builder|ruby|slim|jbuilder))?
          \z
        /x>, @cache=#<ActionView::Resolver::Cache:0x00007f8329b334f0 keys=4 queries=0>, @path="/Users/mathias/rails/ratiofill/auth/app/views">, #<ActionView::OptimizedFileSystemResolver:0x00007f8329b380b8 @pattern=":prefix/:action{.:locale,}{.:formats,}{+:variants,}{.:handlers,}", @unbound_templates=#<Concurrent::Map:0x00007f8329b38090 entries=0 default_proc=nil>, @path_parser=#<ActionView::Resolver::PathParser:0x00007f832c8a9240>, @cache=#<ActionView::Resolver::Cache:0x00007f8329b33ef0 keys=3 queries=0>, @path="/Users/mathias/.rvm/gems/ruby-3.0.2/gems/actiontext-6.1.4.1/app/views">, #<ActionView::OptimizedFileSystemResolver:0x00007f8329b39fd0 @pattern=":prefix/:action{.:locale,}{.:formats,}{+:variants,}{.:handlers,}", @unbound_templates=#<Concurrent::Map:0x00007f8329b39c10 entries=0 default_proc=nil>, @path_parser=#<ActionView::Resolver::PathParser:0x00007f832c8a9218>, @cache=#<ActionView::Resolver::Cache:0x00007f8329b39530 keys=3 queries=0>, @path="/Users/mathias/.rvm/gems/ruby-3.0.2/gems/actionmailbox-6.1.4.1/app/views">]>

Rack Session

#<ActionDispatch::Request::Session:0x00007f8323cc0300 @by=#<ActionDispatch::Session::CookieStore:0x00007f8329f10ca8 @app=#<ActionDispatch::ContentSecurityPolicy::Middleware:0x00007f8329f10e38 @app=#<ActionDispatch::PermissionsPolicy::Middleware:0x00007f8329f10f00 @app=#<Rack::Head:0x00007f8329f110e0 @app=#<Rack::ConditionalGet:0x00007f8329f111d0 @app=#<Rack::ETag:0x00007f8329f11b58 @app=#<Rack::TempfileReaper:0x00007f8329f11d10 @app=#<Rodauth::Rails::Middleware:0x00007f8329f11e00 @app=#<ActionDispatch::Routing::RouteSet:0x00007f8329d8f668>>>, @cache_control="max-age=0, private, must-revalidate", @no_cache_control="no-cache">>>>>, @default_options={:path=>"/", :domain=>nil, :expire_after=>nil, :secure=>false, :httponly=>true, :defer=>false, :renew=>false}, @key="_auth_session", @cookie_only=true, @same_site=nil>, @req=#<ActionDispatch::Request POST "http://localhost:3000/change-login" for 127.0.0.1>, @delegate={"session_id"=>"bfe6640c0352c3c7fa45afc8739dedda", "account_id"=>"9c7d96b8-16e9-43f2-b955-3ab0241f9b5a", "last_password_entry"=>1635317348, "authenticated_by"=>["password", "totp"], "_csrf_token"=>"Yp7raAaVDWUooNZgSrL8Ac0_HYuYNxZJ1LPbl5IHIFw=", "two_factor_auth_setup"=>true}, @loaded=true, @exists=true>

Local Variables

r              #<RodauthApp::Middleware::RodaRequest POST /change-login>

Instance Variables

@_request      #<RodauthApp::Middleware::RodaRequest POST /change-login>
@_response     #<RodauthApp::Middleware::RodaResponse 500 {} []>
@_rodauth      #<#<Class:0x00007f83233c6e98>:0x00007f832b3284c0 @scope=#<RodauthApp::Middleware request=#<RodauthApp::Middleware::RodaRequest POST /change-login> response=#<RodauthApp::Middleware::RodaResponse 500 {} []>>, @rails_controller_instance=#<RodauthController:0x0000000000ba68>, @account={:id=>"9c7d96b8-16e9-43f2-b955-3ab0241f9b5a", :email=>"[email protected]", :status=>"verified"}, @has_password=true, @login_requirement_message="same as current login", @field_errors={"login"=>"invalid login, same as current login"}>
    # app/lib/rodauth_app.rb

    # List of authentication features that are loaded.
    enable :create_account, :verify_account, :verify_account_grace_period,
      :login, :logout, :remember,
      :reset_password, :change_password, :change_password_notify,
      :change_login, :verify_login_change,
      :close_account,
      :confirm_password, :password_grace_period,
      :otp, :recovery_codes

I have some views generated. Listed below. Note that I haven't generated the change_login views yet.

_field_error.html.erb
_field.html.erb
_otp_auth_code_field.html.erb
_password_field.html.erb
_recovery_code_field.html.erb
_recovery_codes_form.html.erb
_submit.html.erb
add_recovery_codes.html.erb
otp_auth.html.erb
otp_disable.html.erb
otp_setup.html.erb
recovery_auth.html.erb
recovery_codes.html.erb
two_factor_auth.html.erb
two_factor_disable.html.erb
two_factor_manage.html.erb

Running rails generate rodauth:views change_login creates three more files.

   identical  app/views/rodauth/_field.html.erb
   identical  app/views/rodauth/_field_error.html.erb
      create  app/views/rodauth/_login_field.html.erb
      create  app/views/rodauth/_login_confirm_field.html.erb
   identical  app/views/rodauth/_password_field.html.erb
   identical  app/views/rodauth/_submit.html.erb
      create  app/views/rodauth/change_login.html.erb

After that, it works as expected. It renders the form with the input and the error message (invalid login, same as current login)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.