0

I added inheritance to my Spree::User model class with STI. I have a :type column which can be (Spree::Guest, Spree::Writer, or Spree::Reader).

In my authentication in the admin side I want to authenticate only writer and reader. What would be the best option to solve this issue?

I tried to override the create action to something like:

def create
  authenticate_spree_user!

  if spree_user_signed_in? && (spree_current_user.role?(:writer) || spree_current_user.role?(:reader))
    respond_to do |format|
      format.html {
        flash[:success] = Spree.t(:logged_in_succesfully)
        redirect_back_or_default(after_sign_in_path_for(spree_current_user))
      }
      format.js {
        user = resource.record
        render :json => {:ship_address => user.ship_address, :bill_address => user.bill_address}.to_json
      }
    end
  else
    flash.now[:error] = t('devise.failure.invalid')
    render :new
  end
end

In this case when trying to authenticate with user of type :guest, it redirects to the new action with invalid failure message (ok) but somehow the user get authenticated (nok).

Moh
  • 249
  • 3
  • 15

1 Answers1

1

I don't think that is a good way to solve that, controller should be just a controller. I'd rather go that way:

Spree uses cancancan (or cancan in older branches) for authorization and that's how Spree implements that. I don't know why you want that STI solution - I would simply create new custom Spree::Role for that but as I said I don't know why you chose STI way - that should work fine too. Anyway, you can either just add a decorator for that ability file with additional checks for something like user.is_a? Spree::Guest and so on or register new abilities via register_ability - something like this.

Most important part of third link (or in case it goes off):

# create a file under app/models (or lib/) to define your abilities (in this example I protect only the HostAppCoolPage model):

Spree::Ability.register_ability MyAppAbility

class MyAppAbility
  include CanCan::Ability

  def initialize(user)
    if user.has_role?('admin')
      can manage, :host_app_cool_pages
    end
  end

end

Personally I would go with decorator option (code seems a bit unclear but is cleaner when it comes to determine what can be managed by who - remember about abilities precedence) but it is up to you. If you have any specific questions feel free to ask, I will help if I will be able to.

Edit: so if you want to disable authentication for some users maybe just leverage existing Devise methods? Something like this(in your user model):

def active_for_authentication?
  super && self.am_i_not_a_guest? # check here if user is a Guest or not
end

def inactive_message
  self.am_i_not_a_guest? ? Spree.t('devise.failure.invalid') : super # just make sure you get proper messages if you are using that module in your app
end
zrl3dx
  • 7,699
  • 3
  • 25
  • 35
  • Thanks zrl3dx for the answer and the solution. I am using STI because I want that my users of different types would be able to register and authenticate from several platforms depending in their types. So a user of type Spree::Guest can register in another platform as Spree::Reader but I want that in each platform the authenticated user must be the right one. For that I use uniqueness en email with scope: :type. On the other hand, could you please explain me what can manage, :host_app_cool_pages does? – Moh Jul 15 '15 at 09:26
  • @Moh: that means admin can do anything with model `HostAppCoolPages` - you can also add other predefined (i.e. read) or your custom actions, check it here: https://github.com/CanCanCommunity/cancancan/wiki/defining-abilities#the-can-method – zrl3dx Jul 15 '15 at 10:34
  • Yes, I have override my ability model class and each user have access or can manage specific models and controllers. But my issue actually is in the authentication, for example I want that user of type Spree::Guest will never have access to the platform where others like Spree::Reader and Spree::Writer can access. Do you think your answer still valid for my problem? if so or if I didn't explain well please let me know. – Moh Jul 15 '15 at 12:26
  • Hi @zrl3dx, I did a mistake with the methods, now they seem to be working. Moreover, I am still facing a critical case, I may be two users A (Spree::Gest ) and B (Spree::Reader) with the same email address but of different types, my doubt is devise or spree_auth_devise don't identify which user must take in account for authentication, I think the first one will be considered (if user A is found first, B will not be considered to authenticate in case is the user I want to be in login). Maybe this feature requires to override the create action or other devise methods? – – Moh Jul 16 '15 at 10:50
  • Hi @Moh, I belive that's out of the scope of that question, that's another issue. Anyway, if you have duplicated emails then you have to get some unique value, e.g. login or something else. You can configure it like that: https://github.com/plataformatec/devise/wiki/How-To:-Allow-users-to-sign-in-using-their-username-or-email-address – zrl3dx Jul 16 '15 at 11:06
  • Excellent, modifying the find_for_database_authentication method fixed the issue. I mentioned that I use uniqueness in email with scope: :type in the first comment, it is my bad I forgot to notice it in the main post. Thanks a lot @zrl3dx – Moh Jul 16 '15 at 12:47