Search code examples
ruby-on-railsdevisewarden

Override Devise controller -- only user with admin role to log in?


How do you override the Devise controller to only allow 'admins' to log in?

This is what I came up with:

class SessionsController < Devise::SessionsController

  def create
    if current_user.admin?
    #   tell the user "you can't do that"
    else
      super
    end
  end

end

but the user was able to log in (probably because 'current_admin' is not defined yet?). Here is the original devise controller action:

class Devise::SessionsController < DeviseController
  prepend_before_filter :require_no_authentication, only: [:new, :create]
  prepend_before_filter :allow_params_authentication!, only: :create
  prepend_before_filter :verify_signed_out_user, only: :destroy
  prepend_before_filter only: [:create, :destroy] { request.env["devise.skip_timeout"] = true }


...


  # POST /resource/sign_in
  def create
    self.resource = warden.authenticate!(auth_options)
    set_flash_message(:notice, :signed_in) if is_flashing_format?
    sign_in(resource_name, resource)
    yield resource if block_given?
    respond_with resource, location: after_sign_in_path_for(resource)
  end

...

end

Edit: I don't think I should change the session controller, I think I should add a strategy to Warden. I tried this and it still logs in non admin users:

config/initializers/custom_warden_strategies.rb:

Warden::Strategies.add(:admin_only) do
  def authenticate!
    resource = password.present? && mapping.to.find_for_database_authentication(authentication_hash)
    encrypted = false
    if validate(resource) { encrypted = true; resource.valid_password?(password) }
      if resource.admin?
        remember_me(resource)
        resource.after_database_authentication
        success!(resource)
      end
    end
    mapping.to.new.password = password if !encrypted && Devise.paranoid
    fail(:not_found_in_database) unless resource
  end
end

config\initializers\devise.rb

  config.warden do |manager|
    manager.default_strategies.unshift :admin_only
  end

Solution

  • I found a solution, but it fails with my test suite (works when I manually test it though).

    config/initializers/admin_only_initializer.rb

    require 'devise/strategies/authenticatable'
    
    module Devise
      module Strategies
        # Default strategy for signing in a user, based on their email and password in the database.
        class AdminOnly < Authenticatable
          def authenticate!
            resource  = password.present? && mapping.to.find_for_database_authentication(authentication_hash)
            encrypted = false
    
            if validate(resource){ encrypted = true; resource.valid_password?(password) }
              if resource.admin?
                success!(resource)
              else
                fail!(:not_permitted)
              end
            end
            mapping.to.new.password = password if !encrypted && Devise.paranoid
            fail(:not_found_in_database) unless resource
          end
        end
      end
    end
    
    Warden::Strategies.add(:admin_only, Devise::Strategies::AdminOnly)
    

    config/initializers/devise.rb

    config.warden do |manager|
        manager.default_strategies(:scope => :user).unshift :admin_only
    end
    

    and the I18n string (config/locales/devise.en.yml):

    en:
      devise:
        failure:
          not_permitted: "You are not permitted to complete this action."