agusgambina
agusgambina

Reputation: 6669

Devise define routes and before_filter for users with admin attribute

I have an small app where I implemented devise. The model where I added Devise is consultant instead of user.

class Consultant < ApplicationRecord
  devise :database_authenticatable, :recoverable, :rememberable, :trackable, :validatable, :confirmable

  def admin?
    self.admin == true
  end

end

I also added an attribute like it suggest here the option 2 to identify admin users.

My goal is to achieve that only admin consultants have access everywhere. Consultants that are not admins should only have access to resources :tasks and get '/tasks/consultants/:id/worked', to: 'tasks#worked'. Visitors should be redirected to the sign_in.

For that purpose I am adding a before_filter in ApplicationController like this

class ApplicationController < ActionController::Base

  before_filter :authenticate_admin!
  skip_before_filter :authenticate_admin!, only: [:tasks]

  private
  def authenticate_admin!
    current_consultant.try(:admin?)
  end

end

and in tasks_controller.rb I added this before_filter

class TasksController < ApplicationController
  before_action :set_task, only: [:show, :edit, :update, :destroy]

  before_filter :authenticate_consultant!

  ...

and my routes.rb is defined like this

Rails.application.routes.draw do

  devise_for :consultants
  devise_scope :consultant do
    authenticated :consultant do
      root 'tasks#index'
    end

    root to: "devise/sessions#new"
  end

  get '/home', to: 'static_pages#home'
  get '/help', to: 'static_pages#help'

  get   '/tasks/consultants/:id/worked', to: 'tasks#worked'

  resources :tasks
  resources :consultants

end

I don't understand why is not validating the users, any user can access anywhere


Update 1

just an small note, I changed every before_filter for before_action because before_filter is deprecated

As @Raffael suggested I updated my application_controller.rb as

class ApplicationController < ActionController::Base

  before_action :authenticate_admin!

  def authenticate_admin!
    unless current_consultant.try(:admin?)
      flash[:error] = 'Shoo, this is not for you'
      redirect_to root_path
    end
  end

end

But I get an error

localhost redirected you too many times.

I think this is happening because I am redirecting from routes.rb and application_controller.rb. When it tries to access devise/sessions#new needs to be authenticaded.

I tried to avoid this by adding the following to the before_action in the application_controller.rb

before_action :authenticate_admin!, :except => ['devise/sessions#new']

Update 2: Solution

Finally this is what I have done, first I created a new file called admin_controller.rb

class AdminController < ApplicationController

  before_action :authenticate_admin!

  protect_from_forgery with: :null_session

  def authenticate_admin!
    unless current_consultant.try(:admin?)
      flash[:error] = 'Shoo, this is not for you'
      redirect_to root_path
    end
  end

end

Second for controllers that need admin privileges I extend from the new class like this

class AnyController < AdminController

The application_controller.rb

class ApplicationController < ActionController::Base

  before_action :authenticate_consultant!, :except => ['devise/sessions#new']

  protect_from_forgery with: :null_session

end

and in routes.rb

Rails.application.routes.draw do

  devise_for :consultants

  root to: 'tasks#index'
...

Is not an elegant solution, but it works. for example it would be better to have all the admin controllers in its own namespace.

Upvotes: 1

Views: 267

Answers (1)

Raffael
Raffael

Reputation: 2669

Returning a falsey value form a filter won't halt the filter chain any more (it did in previous versions of Rails).

If you want your before_filter to prevent the controller action from being executed, you need to redirect or render.

For example something along the lines of:

def authenticate_admin!
  unless current_consultant.try(:admin?)
    flash[:error] = 'Shoo, this is not for you'
    redirect_to root_path
  end
end

or:

def authenticate_admin!
  unless current_consultant.try(:admin?)
    flash[:error] = 'You need to be logged in as an admin to use this resource'
    render 'login_dialog'
  end
end

Upvotes: 1

Related Questions