11
votes

I use the Rails Stack with

  • devise
  • warden
  • confirmable

Now I have a certain requirement related to email confirmation and access provision to unverified users. Let's say there are 3 categories of pages:

  1. case 1 - requires no authentication.
  2. case 2 - requires authentication and also require the user to be confirmed.
  3. case 3 - requires authentication (correct username and password combination) but user need not be confirmed to access them.

With devise and confirmable, implementing case 1 and case 2 is a breeze. Once the user does login/signup, I redirect to "confirm your email page".

My problem is with case 3. Suppose the user does his login/signup. He is yet to confirm his email address but should be allowed to visit certain case 3 routes. When he visits a case 3 routes, I expect:

  • no redirection
  • valid session

Devise with confirmable either allows all the pages to be visited by confirmed users or none. It does not allow access to certain pages with authentication but without confirmation.

I tried overriding the devise confirmed? by implementing this logic:

class ApplicationController < ActionController::Base
   before_filter :verify_user
   def verify_user
       $flag = true if CERTAIN_ROUTES.include?(action_class)
   end
end

class User < ActiveRecord::Base
   def confirmed?
      $flag || !!confirmed_at
   end
end

This barely works for sign in but not for sign up. Also, this is an extremely bad way to achieve it. How should I approach this problem? Other functionalities work fine.

2

2 Answers

8
votes

Instead of overwriting confirmed? you could just overwrite the confirmation_required? model method (docs):

# user.rb
class User < ActiveRecord::Base
  protected

  def confirmation_required?
    false
  end
end

After that you can handle the confirmation logic yourself, by redirecting all unconfirmed users in a before_action when the controllers require confirmation, or you can pull this into your authorization logic (e.g. with pundit).

class ApplicationController < ActionController::Base
   def needs_confirmation
     redirect_to root_path unless current_user.confirmed?
   end
end

class SomeController < ApplicationController
  before_action :needs_confirmation
end
3
votes

You should take a look at the gem 'pundit' - it works well with devise.

https://github.com/varvet/pundit

Rather than writing controller before_actions etc, you write policies which will cover each of your authorization requirements, and then use those policies inside your controllers.

For example, in a controller:

class ExampleController < ApplicationController
    before_action { authorize :example }

    def case_one
       # action
    end

    def case_two
       # action
    end

    def case_three
       # action
    end
end

Then your policy would be kept under app/policies/example_policy.rb

class ExamplePolicy < ApplicationPolicy
  attr_reader :user

  def initialize(user, _)
    @user = user
  end

  def case_one?
    true
  end

  def case_two?
    user.present? && user.confirmed_at.present?
  end

  def case_three?
    user.present?
  end
end

It works really well, especially in other cases where you are determining authorization against a type of resource.