Devise - Respond with resource and custom errors - ruby-on-rails

I only want some users to be able to reset their passwords provided they have a flag set against their profile. I have the current code
class PasswordsController < Devise::PasswordsController
def create
self.resource = resource_class
u = User.find_or_initialize_with_errors([:email], resource_params, :not_found)
if u.try(:group).nil?
super
else
#respond_with(resource)
redirect_to :new_user_password, notice: "Your password is managed by your team leader, please contact them."
end
end
end
Although it's commented out above, I am trying to respond with the resource, just as devise does in it's code. If I respond with the current resource I get undefined methodusers_url'`, how can I solve this?
Also, what is the best way to add errors to this resource so that devise_error_messages! will display them correctly.

Related

Overriding devise registration controller for redirect

Devise functionality needs to be customized and the the RegistrationsController is created.
However, the default set-up for the create action is
super do |resource|
end
which in itself is a bit of a black box, as it goes to the superclass. It obvious is wired up for redirection. Thus:
super do |resource|
[...]
if #user.save?
redirect_to some_user_attribute_path
else
redirect_to a_parameter_based_path
end
end
is not possible as it will naturally create a
AbstractController::DoubleRenderError in Users::RegistrationsController#create
Devise wikis only deal with successful actions or all-encompassing approaches.
It is a goal to avoid ApplicationController methods, as this use-case has very specific behaviours for only user creation according success or failure (in practice the return is to the same page, but in the case of the failure, is defined via a params[:company][:id] value in lieu of #user.company_id
How can this be achieved?
I think you should override the method completely since you don't want the redirection handling that is after the yield. So, you could do this:
class RegistrationsController < Devise::RegistrationsController
def create
build_resource(sign_up_params)
if resource.save?
redirect_to some_user_attribute_path
else
redirect_to a_parameter_based_path
end
end
end
Note: If you need to sign up the user or any other stuff that devise does, you should copy it from the original method

Ruby on Rails: Edit Sessions controller create action to check if user is active

I need to alter the Sessions controller create method to first check if the user has a column filled out for deactivation_date.
If that column has a value, the user IS deactivated and the sign in authentication should fail in the same way as if the user doesn't exist.
class Users::SessionsController < Devise::SessionsController
def create
logger.debug('create')
self.resource = warden.authenticate!(auth_options)
if self.resource.deactivation_date?
logger.debug('user has been deactivated')
flash.now[:notice] = "Sorry, this account has been deactivated."
else
set_flash_message!(:notice, :signed_in)
sign_in(resource_name, resource)
respond_with resource, location: after_sign_in_path_for(resource)
end
end
end
I've tried overriding the sessions controller as well as the create method.
My code above is only preventing a redirect. Once I refresh the page, the user is authenticated no matter what.
Could someone help point me in the right direction?
The most simple way to do this is really just add a custom valid_for_authentication? method in the resource devise is using, so in Users.rb:
def valid_for_authentication?
super && !deactivation_date?
end
Thanks to this question: Check if user is active before allowing user to sign in with devise (rails)
I was able to simply add a method to my User model and devise picked it up automatically
In my user model:
def active_for_authentication?
super && !self.deactivation_date
end
I didn't know at first the method name was specific to devise. has to be exactly active_for_authentication?

Devise Session Controller. Custom routing on failure

Right now I'm extending the Devise sessions controller. Everything is working fine except that when the password is typed wrong or it can't find the user. it tries to redirect_to the sessions#new. I do not want that for my case I want it to redirect to a custom route because this is coming from a different view than the sessions new view. I know about after_sign_in_path_for but I'm not sure that is what I want because my particular case is the warden.authenticate! method. In the auth_options there is a recall hash. That is where I want to customize my route. Here is my sessions controller.
Session Controller
class SessionsController < Devise::SessionsController
def create
super do |user|
if params[:user][:invitation_id].present?
invitation = Invitation.find(params[:user][:invitation_id])
if !user.accounts.exists?(id: invitation.account.id)
user.accounts << invitation.account
flash[:tip_off] = "You now have access to your project \"#{invitation.account.name}\""
else
flash[:tip_off] = "You are already a member of #{invitation.account.name}"
end
cookies[:account_id] = invitation.account.id
invitation.destroy
user.save
end
end
end
end
As I stated before this works fine I just need a custom routes upon failure of password or email. Right now it goes to sessions#new, that does not work for my case. Any help would be great! Thanks
You can override recall value on auth_option method to redirect to another route on failure.
def auth_option
{ scope: resource_name, recall: "#{controller_path}#another_new_path" }
end

Devise with Confirmable - Redirect user to a custom page when users tries to sign in with an unconfirmed email

With the Confirmable module enabled, Devise will not allow an unconfirmed user to sign in after a predefined period of time has elapsed. Instead the user is redirected back to the sign in page with the flash message "You have to confirm your account before continuing".
This is an undesirable interaction model, as a flash notice does not provide adequate space to properly explain to the user why access has been denied, what "confirm your account" means, provide a link to resend the confirmation, and instructions on how to check your spam folder and so on.
Is there a way I can change this behaviour to redirect to a specific URL instead?
Sorry at first I thought you meant after Sign Up not Sign In. So the down below works for how to direct users after Sign Up and what you need to do for Sign In is to create a custom Devise::FailureApp
See the wiki page: https://github.com/plataformatec/devise/wiki/How-To:-Redirect-to-a-specific-page-when-the-user-can-not-be-authenticated
Then within your custom FailureApp overwrite redirect_url method from https://github.com/plataformatec/devise/blob/master/lib/devise/failure_app.rb:
def redirect_url
if warden_message == :unconfirmed
custom_redirect_path
else
super
end
end
For custom redirect after Sign Up:
There is a controller method after_inactive_sign_up_path_for within the RegistrationsController that you can overwrite to accomplish this.
First in your Routes you will need to specify to use your custom controller:
config/routes.rb:
devise_for :users, :controllers => { :registrations => "users/registrations" }
Second you create your custom controller that inherits from the normal controller in order to overwrite the method:
app/controllers/users/registrations_controller.rb
class Users::RegistrationsController < Devise::RegistrationsController
protected
def after_inactive_sign_up_path_for(resource)
signed_up_path
end
end
In this case for my App my Devise model is User so you may want to change that namespace if your model is named differently. I wanted my users to be redirected to the signed_up_path, but you can change that to your desired path.
I just did this, but took a different approach.
in app/controllers/sessions_controller.rb:
class SessionsController < Devise::SessionsController
before_filter :check_user_confirmation, only: :create
#
# other code here not relevant to the example
#
private
def check_user_confirmation
user = User.find_by_email(params[:email])
redirect_to new_confirmation_path(:user) unless user && user.confirmed?
end
end
This worked for me and seemed minimally invasive. In my app new sessions always have to go through sessions#create and users always sign in with their email address, so this may be a simpler case than yours.
You can of course redirect_to any location you desire in the check_user_confirmation method. new_confirmation_path was the logical choice for me because it provides users with the resources to get confirmed.
This is my solution you need to add :unconfirmed message on devise locales below the sessions.
in app/controllers/sessions_controller.rb
def check_user_confirmation
user = User.where(email: params[:user][:email]).take
unless user && user.confirmed?
set_flash_message! :alert, :unconfirmed
expire_data_after_sign_in!
respond_with user, location: after_inactive_sign_up_path_for(user)
end
end
protected
def after_inactive_sign_up_path_for(resource)
new_user_session_path
end

Rails Security: redirect if not current_user

I've been using authlogic and it works really well. One thing I've noticed is that as a hacker I could easily type in this address:
localhost::3000/users/a_user_that_is_not_me/edit
Since the form in the edit form is for #user which is set to current_user and requires an authenticity token, even if I tried to put in details for the other user I end up changing my own account instead of the other users.
That's nice and good, but I'd like it so that these hackers get redirected before they even see the form.
I tried this in the users_controller:
def edit
if admin?
#user = params[:user]
elsif User.find_by_username(params[:id]) != current_user
#user = current_user
#not_user = User.find_by_username(params[:id])
redirect_to user_path(#not_user)
else
#user = current_user
end
end
The redirect works if I type in an address with another user's name but I get a 404 error when trying to access the edit page for the current user.
Any ideas why this doesn't work?
If you're going to be doing this kind of thing a lot, check out an authorization plugin like authorization-san.
Authorization differs from authentication in that authentication is logging in, but authorization pertains to the authenticated (or un-authenticated) user's rights to perform actions.
With authentication-san, you could define this rule with this piece of code in your controller:
# this assumes you've got some way to set #user to the user you're looking up,
# e.g. in a before_filter
allow_access(:authenticated, :only => [:edit, :update]) { current_user == #user }
It looks like you are assigning #user to a string if the current user is an admin. This is simpler (less typo-prone):
def edit
u = User.find_by_username!(params[:id])
if admin? or current_user.username == params[:id]
#user = u
else
redirect_to user_path(u)
end
end
Also, don't you want to use find_by_username! (with bang on end) so that a 404 page is rendered when the user is not found? I'm not sure how you're getting the 404 page now...

Resources