Devise and ActsAsTenant not playing nicely together

1.4k Views Asked by At

I am using ActsAsTenant, and I keep getting the error below on any Devise route (i.e. any Devise controller). It seems that Devise tries to get the current_user or something to do with getting a User before the tenant has been set, so ActsAsTenant then raises an error. I tried using a prepend_before_action to set the tenant but that didnt work.

class ApplicationController < ActionController::Base  
    protect_from_forgery with: :exception

    prepend_before_action :secure_app
    before_action :authenticate_user!

    private

    def secure_app
        self.class.set_current_tenant_by_subdomain_or_domain
    end
end

How do I make sure that the tenant is set before Devise starts looking for the current_user?

ActsAsTenant::Errors::NoTenantSet at /edit ActsAsTenant::Errors::NoTenantSet

block in User.acts_as_tenant
() home/lee/.rvm/gems/ruby-2.1.1/bundler/gems/acts_as_tenant-1b7d146d750b/lib/acts_as_tenant/model_extensions.rb, line 54
block (3 levels) in User.build_default_scope
activerecord (4.1.4) lib/active_record/scoping/default.rb, line 103
User::ActiveRecord_Relation#scoping
activerecord (4.1.4) lib/active_record/relation.rb, line 285
block (2 levels) in User.build_default_scope
activerecord (4.1.4) lib/active_record/scoping/default.rb, line 103
block in User.build_default_scope
activerecord (4.1.4) lib/active_record/scoping/default.rb, line 102
User.evaluate_default_scope
activerecord (4.1.4) lib/active_record/scoping/default.rb, line 125
User.build_default_scope
activerecord (4.1.4) lib/active_record/scoping/default.rb, line 101
User.default_scoped
activerecord (4.1.4) lib/active_record/scoping/named.rb, line 33
User.all
activerecord (4.1.4) lib/active_record/scoping/named.rb, line 28
User.where
activerecord (4.1.4) lib/active_record/querying.rb, line 10
OrmAdapter::ActiveRecord#get
orm_adapter (0.5.0) lib/orm_adapter/adapters/active_record.rb, line 17
User.serialize_from_session
devise (3.2.4) lib/devise/models/authenticatable.rb, line 208
block (2 levels) in Warden::SessionSerializer#user_deserialize
devise (3.2.4) lib/devise.rb, line 462
Warden::SessionSerializer#fetch
warden (1.2.3) lib/warden/session_serializer.rb, line 34
Warden::Proxy#user
warden (1.2.3) lib/warden/proxy.rb, line 212
Warden::Proxy#_perform_authentication
warden (1.2.3) lib/warden/proxy.rb, line 318
Warden::Proxy#authenticate!
warden (1.2.3) lib/warden/proxy.rb, line 127
RegistrationsController#authenticate_user!
devise (3.2.4) lib/devise/controllers/helpers.rb, line 50
RegistrationsController#authenticate_scope!
devise (3.2.4) app/controllers/devise/registrations_controller.rb, line 124
block in ActiveSupport::Callbacks::Callback#make_lambda
activesupport (4.1.4) lib/active_support/callbacks.rb, line 424
block in ActiveSupport::Callbacks::Filters::Before.halting_and_conditional
activesupport (4.1.4) lib/active_support/callbacks.rb, line 143
RegistrationsController#run_callbacks
activesupport (4.1.4) lib/active_support/callbacks.rb, line 86
RegistrationsController#process_action
actionpack (4.1.4) lib/abstract_controller/callbacks.rb, line 19
RegistrationsController#process_action
actionpack (4.1.4) lib/action_controller/metal/rescue.rb, line 29
block in RegistrationsController#process_action
actionpack (4.1.4) lib/action_controller/metal/instrumentation.rb, line 31
4

There are 4 best solutions below

0
On BEST ANSWER

OK unfortunately what I had to do was get rid of Devise and replace it with Clearance, that solved the first round of issues, next up I then got rid of ActsAsTenant and wrote my own little module that on include sets up default scope and throws an error if no tenant is set. All in all a couple of hours work, but everything is much simpler now so it is well worth it

0
On

The reason you get the ActsAsTenant::Errors::NoTenantSet error even after changing your ApplicationController to use a prepend_before_action is because the DeviseController is declared as

class DeviseController < Devise.parent_controller.constantize
...
prepend_before_action :assert_is_devise_resource!
...
end

The parent controller is YOUR ApplicationController. When the DeviseController is instantiated it first runs all the code in your ApplicationController which prepends (puts at the current front of the callback chain) your secure_app method. Only after that can the child class (DeviseController) be instantiated which prepends it's assert_is_devise_resource! callback (which now places it BEFORE your secure_app method).

If you want to have the same users (email or whatever unique external key you use) in multiple tenants, the unscoped solutions above do not work. You can solve this by inheriting from the DeviseControllers so that you can prepend your secure_app method.

routes.rb
devise_for :users, only: %i[session], path: 'users',
             path_names: {sign_in: 'login', sign_out: 'logout'},
             controllers: {
                 sessions: 'users/sessions'
             }
module Users
  class SessionsController < Devise::SessionsController

    # You can access secure_app because SessionsController inherits from your ApplicationController
    prepend_before_action :secure_app

    # GET /users/sign_in
    def new
      super
    end

    # DELETE /users/sign_out
    def destroy
      super
    end

  end
end
0
On

It is possible to workaround this, you can see it in this bugreport: https://github.com/ErwinM/acts_as_tenant/issues/49#issuecomment-77142527

1
On

This is an old post, but the only one that asks about this exact issue, and there's no real solution. I was getting an error when attempting to use devise and acts_as_tenant without subdomains. I wanted to look up the tenant based on the user. This is the error I was getting: ActsAsTenant::Errors::NoTenantSet.

There are solutions here, but these didn't work for me.

The solution I found was to override some methods that Devise uses to extend the User model, so that these methods use unscoped as shown here. Using devise 4.6.2, acts_as_tenant 0.4.3, Rails 5.2.3

app/models/devise_overrides.rb

module DeviseOverrides
  def find_for_authentication(conditions)
    unscoped { super(conditions) }
  end

  def serialize_from_session(key, salt)
    unscoped { super(key, salt) }
  end

  def send_reset_password_instructions(attributes={})
    unscoped { super(attributes) }
  end

  def reset_password_by_token(attributes={})
    unscoped { super(attributes) }
  end

  def find_recoverable_or_initialize_with_errors(required_attributes, attributes, error=:invalid)
    unscoped { super(required_attributes, attributes, error) }
  end

  def send_confirmation_instructions(attributes={})
    unscoped { super(attributes) }
  end

  def confirm_by_token(confirmation_token)
    unscoped { super(confirmation_token) }
  end
end

Then in app/models/user.rb:

class User < ApplicationRecord
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
  devise :invitable, :database_authenticatable, :registerable, :confirmable,
         :recoverable, :rememberable, :validatable

  belongs_to :account
  accepts_nested_attributes_for :account

  acts_as_tenant(:account)
  extend DeviseOverrides
end

Now you can add a config/intializers/acts_as_tenant.rb and everything will continue to work.

ActsAsTenant.configure do |config|
  config.require_tenant = true
end

My app/controllers/application_controller.rb looks like this:

class ApplicationController < ActionController::Base
  set_current_tenant_through_filter
  before_action :set_tenant
  before_action :authenticate_user!

  private

  def set_tenant
    current_account = current_user.account
    set_current_tenant(current_account)
  end
end