Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

throw :warden not always caught by failure_app #198

Open
raquelhortab opened this issue Mar 29, 2021 · 3 comments
Open

throw :warden not always caught by failure_app #198

raquelhortab opened this issue Mar 29, 2021 · 3 comments

Comments

@raquelhortab
Copy link

raquelhortab commented Mar 29, 2021

Hi! I have a project in which there are two kinds of user and both of them are set up with devise. When the sing in fails for one kind of user (Pc), I wish to try if the credentials are correct for the other one (Tutor), since some Tutor users forget to use their specific login page. I created a custom failure app in which I recreate the call but for the other resource name. It works correctly if there is a user of type Tutor with the credentials. However, if the second attempt fails the failure app is not called again and the warden error is not caught.
I can't see why it would be called the first time but not the second. While debugging I have checked that warden.manager.config.failure_app keeps being my custom failure app through all the calls. Could anyone tell me if this is the expected behaviour? These are some key parts of the code:


class CustomFailureApp < Devise::FailureApp

  def respond
    if warden_options[:scope] == :pc && (warden_message == :invalid || warden_message == :not_found_in_database)
      params[:tutor] = params[:pc] # copy the credentials as if they were tutor credentials
      params.except!(:pc) # remove pc params
      flash.keep # it gets cleared otherwise, which leads to errors
      request.env["devise.mapping"] = Devise.mappings[:tutor]
      sessions_controller = SessionsController.new
      sessions_controller.request = request
      sessions_controller.response = response
      sessions_controller.process(:create)
    else
      super
    end
  end

#... other unrelated code
config.warden do |manager|
    manager.failure_app = CustomFailureApp
  end

What I observed is that in proxy.rb, in:

   def authenticate!(*args)
      user, opts = _perform_authentication(*args)
      throw(:warden, opts) unless user
      user
    end

the first time, CustomFailureApp is called after throw(:warden, opts) unless user but on the second time it is not, it continues and leads to some other errors related to user being nil here

If you think this is the expected behaviour an I should rather post this somewhere else like stackoverflow please say so.

Edit: I did post in on stackoverflow as well (https://stackoverflow.com/questions/66866653/throw-warden-not-always-caught-by-failure-app)

Thanks!

@glaszig
Copy link

glaszig commented Nov 3, 2023

i'm getting hit by this when using rails' AC::Live module. appearently the issue is that, according to the rack spec, you cannot throw/catch between middlewares. i don't know how to resolve this.

see rails/rails#20858

@glaszig
Copy link

glaszig commented Nov 3, 2023

more context: rails/rails#25581 (comment)

# Also handle the unauthenticated cases where warden would throw a
# :warden symbol, but it would cause an exception because the live
# controller runs in a separate thread and only propagates exceptions.
# There is currently no way to proparate throws through the Thread
# boundary.
#
# To handle this situation, we detect an ArgumentError caused by
# an uncaught throw by warden, then we rethrow the :warden symbol
# without any parameters. This will cause the appropriate warden
# handlers to run. Note that custom actions or any other parameters
# to throw :warden are lost.
#
# https://github.com/hassox/warden/wiki/Failures
# https://bugs.ruby-lang.org/issues/6372
# https://github.com/plataformatec/devise/issues/2332
# https://github.com/rails/rails/issues/13873

@qquokka
Copy link

qquokka commented Aug 27, 2024

Here’s what I found related to this issue:

When a request is made to a controller that includes the ActionController::Live module, it is processed through the process method of the ActionController::Live module. This method overrides the process method in ActionController::Base.

The problem arises because the ActionController::Live#process method creates an additional thread. The existing thread is responsible for streaming the response, while the new thread handles the action.

If an exception is raised in the new thread, it is configured to also raise the same exception in the parent thread.

However, the devise-security gem transfers control to the previous stack not by raising an exception, but by throwing an error. When a throw occurs, it passes a specific token that is supposed to be caught by the corresponding stack. The issue seems to be that the thread that throws (child thread) and the thread that catches (parent thread) are different, leading to the UncaughtThrowError (uncaught throw :warden) error.

As a reference, a similar issue was raised in the Rails repository, but it was closed with a comment stating that having different gems throw/catch each other is a violation of the Rack spec.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants