janko / rodauth-rails

Rails integration for Rodauth authentication framework
https://github.com/jeremyevans/rodauth
MIT License
571 stars 40 forks source link

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

Closed gruschis closed 2 years ago

gruschis commented 2 years ago

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=>"test@test.com", :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)

janko commented 2 years ago

Thanks for reporting, it should indeed work without generating the template, I'll look into it. You didn't by any chance change layout settings in your RodauthController?

gruschis commented 2 years ago

No. Here's the controller

# app/controllers/rodauth_controller.rb

class RodauthController < ApplicationController
  # used by Rodauth for rendering views, CSRF protection, and running any
  # registered action callbacks and rescue_from handlers

  def download_recovery_codes
    send_data rodauth.recovery_codes.join("\n"),
              filename: 'myapp-recovery-codes.txt',
              type: 'text/plain'
  end
end
janko commented 2 years ago

I'm not able to reproduce the error with a fresh Rails 6.1 app. I've generated views for otp, sms_codes, and recovery_codes, and tried changing the login to the current email address, but I get the form back with the validation error saying that the new email is the same as the current email.

It would be helpful to get a Rails app that reproduces the issue, but even the backtrace might helpful.

gruschis commented 2 years ago

Here's the repo: https://github.com/gruschis/argument-error

Please note, that this case was not a fresh install.

I had added a couple of gems, with attention to rails' new importmap. When commenting out import "@hotwired/turbo-rails" in the app/javascript/application.js it doesn't throw the error. I'm not certain if turbo or importmap is the culprit here.

I hope that helps.

/ edit: I also wanted to add that this issue might be out of scope here :)

janko commented 2 years ago

Thanks for sharing, there is indeed some incompatibility with Turbo in rodauth-rails, specifically with Turbo sending the turbo stream MIME type in the Accept request header. I'll work on a fix.

janko commented 2 years ago

So, when Turbo sends a request, a controller will have configured its formats to [:turbo_stream, :html], indicating that it can render both turbo_stream and html templates. This works with normal rendering, but when rodauth-rails uses render html: "..." to render Rodauth's built-in templates, Rails takes only for first available format, which in this case is :turbo_stream. It then tries to find a layout with turbo_stream format, which causes the error. It seems that passing the :formats option to render should resolve the issue, I'll try that.