Before Action Authenticate with Devise and STI not working - ruby-on-rails

I'm trying to use before_action :authenticate_admin! to determine if the current user is an admin or not. I am using devise with single table inheritance (STI).
Here is my User model;
class User < ApplicationRecord
def admin?
false
end
devise :database_authenticatable, :recoverable, :rememberable, :trackable, :validatable, :lockable
end
And my Admin model;
class Users::Admin < User
def devise_scope
:admin
end
def admin?
true
end
end
Just to test out devise, I have this in my routes file;
authenticated :user, ->(u) { u.admin? } do
resources :teams
end
And indeed, if I am signed in as a different User type (so not Admin), I can't get to that resource.
But this fails to work;
class Teams < ApplicationController
before_action :authenticate_admin!
def index
end
end
I get the error;
undefined method `authenticate_admin!'
Quite new to Rails so I don't understand how to resolve this.
UPDATE
I had to put this in the routes file;
devise_for :admins, class_name: 'Users::Admin'
Previously I only had;
devise_for :users, controllers: {sessions: 'users/sessions'}

Related

change password for devise admin without :registerable

I want to make Devise Admin not registerable, but when I take :registerable away from admin.rb, the edit_admin_registration_path does not work. In this case, how can an admin change his email/password?
class Admin < ActiveRecord::Base
devise :database_authenticatable,
:recoverable, :timeoutable, :registerable
Error:
undefined local variable or method `edit_admin_registration_path' for #<#<Class:0x8cf7b40>:0x8cf5278>
Note: I have 2 different Devise Models
Removing :registerable from your Admin model will remove all route helpers for the Devise::RegistrationsController, such as: edit_admin_registrations_path.
You will need to keep the :registerable in your Admin model and override the Devise::RegistrationsController to restrict access to new and create actions.
Create a new registrations_controller.rb that contains:
class RegistrationsController < Devise::RegistrationsController
def new
redirect_to root_path
end
def create
redirect_to root_path
end
end
And in your routes.rb tell Devise to use your registrations controller.
devise_for :admins, controllers: { registrations: 'registrations' }
This way you will restrict users from creating accounts, but allow existing ones to edit their info.

STI with Rails and Devise

i'm working on a Ruby on Rails application and using devise to authenticate user.
First of all, i have these models:
class User < ActiveRecord::Base
devise :database_authenticatable, :registerable, :recoverable, :rememberable, :trackable, :validatable, :lockable
end
class Admin < User
end
class Partner < User
end
My devise_for on routes:
devise_for :users, :controllers => {
:registrations => "registrations",
:passwords => "passwords"
}
What i want to do? I want do a separate devise for each model, with different views and different strategy, for example: The user login with his registration number and password, the admin and the partner login with email and password (i think that admin and partner devise can be the same, because thier login stratagy is the same).
What is the best way to do this?
Configure routes:
devise_for :users
devise_for :admins
devise_for :partners
Generate devise views
rails generate devise:views user
rails generate devise:views admin
rails generate devise:views partner
Don't forget, add type field in users table for your STI
class AddTypeToUser < ActiveRecord::Migration
def up
add_column :users, :type, :string
end
def down
remove_column :users, :type
end
end

How to add an admin restriction feature through Devise to my rails app?

My rails app has a few cab operators and they have a few cabs, and they are related as follows:
class Operator < ActiveRecord::Base
has_many :cabs
end
I wish to add authentication system so as to create admins for each operator. I am using Devise. Since I need to create path as: operator/:operator_id/admins/sign_up, I generated the Admin model, as:
rails generate devise Admin
Then I modified my routes so as to obtain the above mentioned path:
scope "operators/:operator_id" do
devise_for :admins
end
After a few more modifications, I was able to add new admins and associate them to appropriate operators. However, I want to ensure that an admin only has access to the cabs of the operator to which the admin is associated. Adding the following to the cabs_controller doesn't work:
before_action :authenticate_admin!
as a signed_in admin has access to all other operator's cabs. I want to make sure that:
1. If there is no current_admin, my app asks to sign_in or sign_up
2. If there already is a current__admin signed_in, he/she has access to only the cabs associated to that operator to which the current_admin is assigned.
I am new to Devise. Please advise how I should proceed. Thanks!
Devise is for Authentication, your problem is an Authorization problem which Devise cannot help. You can look into Cancan https://github.com/ryanb/cancan :)
You don't need to scope your devise for admins. I'm not sure if it would solve any issue with access. It also makes your code harder to maintain.
I would advise you to focus on relationships instead
class Operator < ActiveRecord::Base
has_many :cabs
has_many :admins
end
class Operator < ActiveRecord::Base
has_many :cabs, through: operator
belongs_to :operator
end
Normally the way I handle such cases later is to write special in the cab model :
def is_admin?(admin)
admin.cabs.include?(#cab)
# you can put more logic here if you have different levels of access
end
and later in the controller when you want to restrict access:
if #cab.is_admin?(current_admin)
# do the stuff
else
# go away message
end
or for instance if you list cabs in index:
def index
current_admin.cabs.page(params[:page]) # this is assuming you're using pagination with something like Kaminari
end
current_admin from divise, you may want to also run :authenticate_admin! before filter to make sure you have you admin and it's not nil
Of course you can put it in price message and run it as before filter.
The most simple solution is to use STI and have Admin < User, then a couple of
devise_for :users
devise_for :admins
If you do not want to use STI, some custom overridings may do the trick, if you add a role method to your users table for exemple.
#routes
devise_for :users
namespace :admin do
devise_for :admins,
singular: :admin,
plural: :admins,
class_name: 'User',
only: [:sessions],
path: '/',
module: :devise
end
#lib/overrides/devise
require "devise/strategies/authenticatable"
require "devise/strategies/database_authenticatable"
module Devise
module Strategies
class DatabaseAuthenticatable
def authentication_hash
if mapping.name == :admin
#authentication_hash.update(role: :admin) #will add a where(role: :admin) clause
else
#authentication_hash
end
end
end
end
end
class Admin::ApplicationController < ApplicationController
skip_before_filter :authenticate_user!
before_filter :authenticate_admin!
end
You can use role base Authentication.
Generate User model
class User < ActiveRecord::Base
enum role: [:operator, :admin]
after_initialize :set_default_role, :if => :new_record?
def set_default_role
self.role ||= :operator
end
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
end
generate migration to add role
rails g AddRoleToUser role:integer
Please take a look ENUM

Superclass mismatch for class RegistrationController - Overriding Devise Controller

I am trying to override my devise registration controller, with no luck.
I finally got the routes working, but now I'm getting an superclass mismatch for class error.
Heres my setup:
Registration Controller (app/controllers/users/registrations_controller.rb)
class RegistrationsController < Devise::RegistrationsController
def sign_up_params
devise_parameter_sanitizer.sanitize(:sign_up)
params.require(:user).permit(:email, :password, profile_attributes: [:username])
end
def new
super
end
def create
end
def update
super
end
end
Routes
root 'welcome#index'
devise_for :users, :controllers => {:registrations => "users/registrations"}
Views
--edit.html.erb && new.html.erb exist in the folder (app/views/users/registrations)
User Model (just in case)
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
has_one :profile
accepts_nested_attributes_for :profile
def profile
super || build_profile
end
end
Any idea why this error is appearing?
Thanks!
Your controller is underneath the users directory but does not have a Users module (it is not in the Users namespace, you might say). Either change the controller to this:
module Users
class RegistrationsController < Devise::RegistrationsController
...
end
end
Or move your controller up a directory
app/controllers/registrations_controller.rb
Define your RegistrationsController as below
class Users::RegistrationsController < Devise::RegistrationsController
...
end
By defining the controller as suggested above, you don't need to define the module explicitly.
You get the error because you have placed the RegistrationsController inside users folder.
So, rails expects that RegistrationsController is a class belonging to Users module.

Rails: Using Devise with single table inheritance

I am having a problem getting Devise to work the way I'd like with single table inheritance.
I have two different types of account organised as follows:
class Account < ActiveRecord::Base
devise :database_authenticatable, :registerable
end
class User < Account
end
class Company < Account
end
I have the following routes:
devise_for :account, :user, :company
Users register at /user/sign_up and companies register at /company/sign_up. All users log in using a single form at /account/sign_in (Account is the parent class).
However, logging in via this form only seems to authenticate them for the Account scope. Subsequent requests to actions such as /user/edit or /company/edit direct the user to the login screen for the corresponding scope.
How can I get Devise to recognise the account 'type' and authenticate them for the relevant scope?
Any suggestions much appreciated.
There is an easy way to handle STI in the routes.
Let's say you have the following STI models:
def Account < ActiveRecord::Base
# put the devise stuff here
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
end
def User < Account
end
def Company < Account
A method that is often overlooked is that you can specify a block in the authenticated method in your routes.rb file:
## config/routes.rb
devise_for :accounts, :skip => :registrations
devise_for :users, :companies, :skip => :sessions
# routes for all users
authenticated :account do
end
# routes only for users
authenticated :user, lambda {|u| u.type == "User"} do
end
# routes only for companies
authenticated :user, lambda {|u| u.type == "Company"} do
end
To get the various helper methods like "current_user" and "authenticate_user!" ("current_account" and "authenticate_account!" are already defined) without having to define a separate method for each (which quickly becomes unmaintainable as more user types are added), you can define dynamic helper methods in your ApplicationController:
## controllers/application_controller.rb
def ApplicationController < ActionController::Base
%w(User Company).each do |k|
define_method "current_#{k.underscore}" do
current_account if current_account.is_a?(k.constantize)
end
define_method "authenticate_#{k.underscore}!" do
|opts={}| send("current_#{k.underscore}") || not_authorized
end
end
end
This is how I solved the rails devise STI problem.
I just ran into the exact scenario (with class names changed) as outlined in the question. Here's my solution (Devise 2.2.3, Rails 3.2.13):
in config/routes.rb:
devise_for :accounts, :controllers => { :sessions => 'sessions' }, :skip => :registrations
devise_for :users, :companies, :skip => :sessions
in app/controllers/sessions_controller.rb:
class SessionsController < Devise::SessionsController
def create
rtn = super
sign_in(resource.type.underscore, resource.type.constantize.send(:find, resource.id)) unless resource.type.nil?
rtn
end
end
Note: since your Accounts class will still be :registerable the default links in views/devise/shared/_links.erb will try to be emitted, but new_registration_path(Accounts) won't work (we :skip it in the route drawing) and cause an error. You'll have to generate the devise views and manually remove it.
Hat-tip to https://groups.google.com/forum/?fromgroups=#!topic/plataformatec-devise/s4Gg3BjhG0E for pointing me in the right direction.
try to change routes like so:
devise_for :accounts, :users, :companies
because Devise uses plural names for it's resources
Please let me know if it help you
I don't think this is possible without overriding the sessions controller. Each sign_in page has a specific scope that devise will authenticate against as defined by your routes.
It may be possible to use the same sign_in page for multiple user scopes by using the devise_scope function in your routes file to force both :users and :companies to use the same sign in page (a how-to can be found here), but I'm pretty certain that you would have to modify your sessions controller to do some custom logic in order to determine which type of user is signing in.

Resources