ActiveAdmin over ride after_sign_in_path_for method - ruby-on-rails

I am using Devise gem for Authentication and ActiveAdmin gem which also uses Devise gem as dependency. I want to enable 2FA for ActiveAdmin, that's why I want to over ride after_sign_in_path_for method. I am able to over ride the method in ApplicationController but doesn't sounds right to me because this will affect our normal login as well. Is there any way to over ride after_sign_in_path_for only for ActiveAdmin. Currently this is how I am doing
class ApplicationController < ActionController::Base
def after_sign_in_path_for
end
end
If I can't only over ride only ActiveAdmin controller then how to deal with normal login without 2FA and ActiveAdmin login with 2FA. Something like this.
class ApplicationController < ActionController::Base
def after_sign_in_path_for(resource)
if resource.instance_of?(AdminUser)
redirect_to setup_2fa_path
else
super # <-------- is this possible or is this correct ?
end
end
end
There must be a way where I can only over ride after_sign_in_path_for for ActiveAdmin. Or do I need to over ride ActiveAdmin::Devise::SessionsController class in initializer? Putting controller login inside initializer also don't feel right to me.
UPDATE
I noticed there is one more problem, when control reaches after_sign_in_path then current_admin_user is already set. This is problem because user can skip setting up 2FA and type path in browser like localhost:3000/admin and they are in because there is current_admin_user. I can set current_admin_user to nil in after_sign_in_path but I am not sure if that will open door to other type of attacks ? How should I deal with this ? or shall I try to over ride different method ? I don't know if there is something like before_sign_in_path_for or something else.

An approach is to open up and extend the Devise::SessionsController within ActiveAdmin. I managed to implement 2fa with a one time password based on code discussed at https://medium.com/#acesubido/adding-two-factor-authentication-in-activeadmin-2ed134b60042 and https://medium.com/#acesubido/part-2-adding-two-factor-authentication-in-activeadmin-cc5eab67057c
Also: https://blog.kiprosh.com/adding-two-factor-authentication-2fa-for-activeadmin-auth-in-a-ruby-on-rails-web-application/ can be of inspiration, I chose for the first setup with a 2 step process. Also you may want to consider a before_action in you ApplicationController that allows for users to setup their 2fa after logging in:
def check_2fa
return if current_admin_user.nil?
redirect_to user_dashboard_two_factor_new_path \
if current_admin_user.force_2fa && !current_admin_user.otp_required_for_login
end
Good luck.

Related

Sign out a devise user from a model

In my application, I need to sign out specific users from time to time. And I need to do it from the interface or from a sidekiq worker. So I would like to create a sign_out method in my user model.
I saw in the documentation that devise provides a sign_out method but only in the controller. Is there a way to access to this method from a model or something similar.
Thanks
You need to read this answer https://stackoverflow.com/a/24388643/4269732 first.
If I was to implement this behavior then I would have added a column in User Model like expire_at_next_request?
Then just check this value in before_filter and logout the user if this is true.
class ApplicationController < ActionController::Base
before_filter :logout_if_requested
def logout_if_requested
if current_user && current_user.expire_at_next_request?
current_user.update_attributes(:expire_at_next_request=>false)
sign_out current_user
redirect_to :new_session_path
end
end

Devise's current_user nil in ApplicationController but not in a different controller (using Simple Token Authentication)

I have a Rails 3.2.22 app running in production for +1 year which uses Devise to authenticate users.
I'm trying to implement token authentication, so I can send transactional e-mails with URL params that can log in the user automatically, using a Gem named Simple Token Authentication https://github.com/gonzalo-bulnes/simple_token_authentication
After following all the instructions, I replaced before_filter :authenticate_user! in my controllers with acts_as_token_authentication_handler_for User.
The gem has integration with and a default fallback to Devise, so devise doesn't need to be called in the controllers anymore; if the token is missing from the params (or wrong), Devise will take over.
In my tests, if I add this line to ApplicationController, everything works fine and I can log in users using the authentication_token= secret the gem generates.
But I don't need auth for ApplicationController, I need it for other controllers (like DashboardController), url being /dashboard
If I put acts_as_token_authentication_handler_for User in that controller (replacing Devise's call), I get the most bizarre of situations.
Using binding.pry, I can confirm that current_user is correctly set during the loading of the template.
But there comes a point in the template where it uses #last_emails, which is defined inside a method in ApplicationController.
Using binding.pry, I can confirm current_user is nil there.
This is the code:
class DashboardController < ApplicationController
layout 'material'
acts_as_token_authentication_handler_for User
And in ApplicationController:
class ApplicationController < ActionController::Base
layout 'omega'
before_filter :populate_last_contacts_for_menu
private
def populate_last_contacts_for_menu
if current_user
#last_contacts = Contact.where("user_id" => current_user.id).where('blocked != ? or blocked is null', true).last(10).reverse
end
end
Funny thing is: using binding.pry, like I said, I can check that current_user is defined in the template (which means sign_in was a success). It even is defined in the better errors console. But, if I go to homepage, I see that user is not logged in ...
I've looked all over the web for this: read all the issues inside the Gem's github and all posts in SO about current_user being nil, but no light at all.
My devise_for :users is not inside any scope in routes.rb and, as I said, I have many calls to current_user all over the app and this is the first time I have issues with Devise.
When you call the acts_as_token_authentication_handler_for directive in the DashboardController it declares some before_filters for the controller to authenticate a user.
But the problem is that when you inherit rails controllers, at first, filters of a parent controller are executed, then filters of a child controller.
The parent controller is ApplicationController. At the moment when it's populate_last_contacts_for_menu filter is called, the user is not authentacated, because the authenticating filters given by the acts_as_token_authentication_handler_for directive have not called yet, they are declared in the child controller.
Possible solutions:
1) Try to append the populate_last_contacts_for_menu filter:
append_before_filter :populate_last_contacts_for_menu
I am not sure it will work in your case, but you can try and find it out.
2) Call the acts_as_token_authentication_handler_for directive in the ApplicationControoler and somehow skip it for the controllers that don't need it. (I don't like this way, but it may help if the first one will not work. )
3) Move the populate_last_contacts_for_menu filter logic into helpers. I think it is the best solution. This logic doesn't belong to a controller. When requests are not 'get', this filter executes for nothing, because you don't need to render views in that case.
module ApplicationHelper
def last_contacts
#last_contacts ||= if signed_in?
Contact.where("user_id" => current_user.id).where('blocked != ? or blocked is null', true).last(10).reverse
else
[]
end
end
...
end
# View:
<% if last_contacts.present? %>
....
<% end %>

Devise Redirects to specific page based on User Role on Login

I have three possible permissions for a User to be in my Rails app, they are User.is_admin, User.is_school, and User.is_security. Based on the nature of my app I need to have a separate home screen for each of these users that do radically different things, which I have working. The problem that I'm having has to do with how Devise auto redirects to root_path after login for all users, regardless of the permissions I have set.
I generated the Devise Sessions controllers into the Users namespace and I have overwritten it to default to my controller, but now when I try to do a redirect, based on the conditional permissions, I get a DoubleRenderError (The obvious reason being that Devise is redirecting elsewhere when creating the session).
I have tried running it as an after_action and even tried overwriting the after_sign_in_path_for method, as per the direction of the Devise docs on the matter, but I still can't get it working. Any help would be appreciated, thank you!
You can do something like this
class ApplicationController < ActionController::Base
def after_sign_in_path_for(resource)
if resource.role == 'admin'
admin_root_path
else
user_root_path
end
end
end
you can read more about this https://github.com/plataformatec/devise/wiki/How-To:-redirect-to-a-specific-page-on-successful-sign-in

Rails/Devise: execute action after automatic login

EDIT: I use Devise 3.4.1, and after_remembered isn't available. Either apply this small patch or use a newer version released after 2014/11/09.
Alright, so I am rather new to the Rails environment, and I feel I am missing something.
I wish to execute a particular action after login, be it after login from a form or automatic login.
I found out that after_sign_in_path_for is executed after a "regular" login. I already use it in my Application controller.
class ApplicationController < ActionController::Base
[…]
def after_sign_in_path_for(resource)
myAction
end
However, it seems that this method isn't called when a user is logged in through the Devise Remember option.
I found out that after_remembered is executed after a user is automatically logged in, however I don't understand how to use it. I don't want to modify the Devise gem just to add my action to the method, and the following doesn't seem to work:
class ApplicationController < ActionController::Base
[…]
def after_remembered()
myAction
end
I am at a loss here. Either I don't understand how to use after_remembered, or I look at it the wrong way and there is another solution for that (should it cover either both cases or only the case with "remember me" automatic login, is fine for me). Does anyone have any idea to make it work?
PS: I am working on an app I haven't developed myself. If you feel you need more code in order to answer, tell me and I will edit my post.
It's part of the Rememberable module so it belongs in your model... assuming your resource is User the way you would use it is...
class User < ActiveRecord::Base
devise :rememberable
def after_remembered
# (your code here)
end
end
I'm not sure if Devise offers a controller-specific method but if not you could throw one togeter... create an attribute in your User model called, say, signed_in_via_remembered
class User < ActiveRecord::Base
devise :rememberable
def after_remembered
update_attribute(:signed_in_via_remember, true)
end
end
then in your application_controller.rb
class ApplicationController < ActionController::Base
before_action :handle_sign_in_via_rememberable
def handle_sign_in_via_rememberable
if current_user and current_user.signed_in_via_remember?
current_user.update_attribute(:signed_in_via_remember, false)
after_sign_in_path_for(User)
end
end
def after_sign_in_path_for(resource)
myAction
end
end
Someone may have a neater solution.

Rails devise set authorisation on multiple models

I have a standard devise implementation and throughout other models there seems to be no redirect if the session is expired, leaving the user with a error message.
For example on the user profile page if not logged in it will just show an error because the current_user does not exist.
Do i set in each model stating to authorise. Or a better solution, can I set it in the app controller and do it application wide and simply set any public pages wherever necessary?
If you want a particular controller to check if a user is logged in then you want to use Devise's authenticate_user! function. See example below
class StuffController < ApplicationController
before_filter :authenticate_user!
def index
..... more implementation
end
end
If the user is not logged in they will be redirected to the login form and then to the required page after a successful login
Use a before filter such as before_filter :authenticate_user!. Place this in your application controller. authenticate_user! is a devise helper so if you want custom behavior you can overload the method or simply write your own filter using their user_signed_in? helper method.

Resources