Infinite loop with devise timeoutable + rememberable when session expires - SystemStackError (stack level too deep)
Hello, I'm not sure if I should post this here, or on the devise/warden repos. But I'm getting an infinite loop when I use the audited gem with devise "timeoutable". Here is the stack trace I'm seeing:
https://gist.github.com/ndbroadbent/4fd5114ab14d1d3593ea5cb9593ecfc2
I have a custom #current_user method in application_controller.rb that is basically the same as the default Devise one, but adds eager loading for User roles (from the rolify gem.)
def current_user
return @current_user if @current_user
@current_user = warden.authenticate(scope: :user)
ActiveRecord::Associations::Preloader.new.preload(@current_user, :roles)
@current_user
end
I'm not sure why, but this is only crashing with a SystemStackError on production, and it's not crashing locally or on CI. It might be because I have set ulimit to "unlimited" locally and on CI containers, and maybe devise or warden has an internal counter somewhere to detect and prevent infinite loops. I noticed that the current_user method is called around 97 times before it stops trying and moves on, so that's why I think there might be some infinite loop detection happening somewhere. But unfortunately it's not enough to prevent a SystemStackError crash on production.
I'm trying to understand the stack trace, and here's what I think is happening:
- application calls
ApplicationController#current_user - This calls
warden.authenticate(scope: :user) - Devise checks if the session has timed out after inactivity
- If timed out, it signs out the user automatically. Devise "rememberable" calls the
#forget_me!callback to clearremember_tokenandremember_created_aton the User:
def forget_me!
return unless persisted?
self.remember_token = nil if respond_to?(:remember_token)
self.remember_created_at = nil if self.class.expire_all_remember_me_on_sign_out
save(validate: false)
end
- The audited gem calls
#audit_updatefor the User update, and then it calls#set_audit_userto set the current user - This calls
ApplicationController#current_user.@current_useris nil because it has been signed out. - Go back to step 1
(I did notice that my Audited db table was taking a ton of space, so this might be related, since I think it's creating audited records in an infinite loop until it crashes. I might have to check this and delete a bunch of rows.)
Solutions:
- Don't created audited records when updating
remember_tokenandremember_created_atattributes (and possibly other devise attributes.) Maybe set this by default. - Audited could somehow detect if the user is being automatically signed out, maybe via a deeper devise/warden integration. Or even just add it's own infinite loop detection internally
- I'm also going to add test expectations to my own codebase to make sure that the Audited gem never creates too many audit records
Please let me know if you have any other suggestions or feedback. Thanks!
Yup i have the same issue also