Devise authentication logs in after password change - ruby-on-rails

Devise authentication gem in Rails.
How to prevent automatic logging in after password change by "forgot password" link?
Ideally it would be nice to display the page with message "New password has been saved".

You will need to override Devise's passwords_controller which you can see the default methods for here. First, create your own controller which will inherit from the Devise controller:
class User::PasswordsController < Devise::PasswordsController
Once you have your controller ready, add in all of the other methods that you do not want to override, and simply call super inside of them. This will be the new, edit, and create methods. Also don't forget to add the protected after_sending_reset_password_instructions_path_for(resource_name) method.
The method that you are concerned with overriding is the update action.
def update
self.resource = resource_class.reset_password_by_token(resource_params)
if resource.errors.empty?
flash_message = resource.active_for_authentication? ? :updated : :updated_not_active
set_flash_message(:notice, "Your flash message here")
redirect_to new_user_session_path
else
respond_with resource
end
end
All we change here is to remove the line to sign in the user with a redirect to the sign in page, and then set our custom flash message.
Lastly, you have to tell devise to use your new controller, so in routes.rb change devise_for :users to:
devise_for :users, :controllers => { :passwords => 'users/passwords' }
And that should do it.

Here's an update based on 3.1.1 of devise
class Users::PasswordsController < Devise::PasswordsController
def new
super
end
def edit
super
end
def create
super
end
#override this so user isn't signed in after resetting password
def update
self.resource = resource_class.reset_password_by_token(resource_params)
if resource.errors.empty?
resource.unlock_access! if unlockable?(resource)
flash_message = resource.active_for_authentication? ? :updated : :updated_not_active
set_flash_message(:notice, flash_message) if is_navigational_format?
respond_with resource, :location => after_resetting_password_path_for(resource)
else
respond_with resource
end
end
protected
def after_resetting_password_path_for(resource)
new_session_path(resource)
end
end

As of Devise 3.5.0, this behaviour can be controlled with a setting, which can be found in config/initializers/devise.rb:
# When set to false, does not sign a user in automatically after their password is
# reset. Defaults to true, so a user is signed in automatically after a reset.
config.sign_in_after_reset_password = false
The flash message shown will be Your password has been changed successfully., but can be adjusted in config/locales/devise.en.yml:
en:
devise:
passwords:
updated_not_active: New password has been saved

The above said answer is correct but the thing is it varies according to the devise version. I followed the above said and i could not get it working and after some time i found that i am using devise version which does not support resource_params method, then i tried different for that version and got it working.

Related

Customize Devise SessionsController create action

I want to add a feature that will redirect a user with an expired password to the reset password page.
My controller looks like this
class Users::SessionsController < Devise::SessionsController
def create
user = User.find_by(email: params[:user][:email].downcase)
if user.password_expire?
raw, enc = Devise.token_generator.generate(current_user.class,
:reset_password_token)
user.reset_password_token = enc
user.reset_password_sent_at = Time.now.utc
user.save(validate: false)
redirect_to edit_password_url(user, reset_password_token: raw)
else
self.resource = warden.authenticate!(auth_options)
set_flash_message(:notice, :signed_in)
sign_in(resource_name, resource)
yield resource if block_given?
respond_with resource, location: after_sign_in_path_for(resource)
end
end
end
When I debug with binding.pry at the top of the action, I find that current_user exists and user_signed_in? is true. How is it possible I'm signed in before the create method completes?
If you are using the security extension you don't need at all to take care of the implementation of password expiration.
And if you are not using it, you should check it out - devise_security_extension.
Devise remembers the current user using cookies until they log out.
Just because they've hit your sign in route doesn't mean they're signed out.
Diving into Devise
Tracing the code in Devise to understand what happens we see:
1. Devise::SessionsController#create
class Devise::SessionsController < ApplicationController
# ...
def create
# ...
sign_in(resource_name, resource)
# ...
end
end
2. Devise::Controllers::Helpers#sign_in
def sign_in(resource_or_scope, *args)
# ...
if options[:bypass]
warden.session_serializer.store(resource, scope)
elsif warden.user(scope) == resource && !options.delete(:force)
# Do nothing. User already signed in and we are not forcing it.
true # <=== Here's the moment of truth
else
# ...
end
Conclusion
The user can hit your sessions#create when they're already logged in
In this case the default Devise behaviour is to do nothing
Not quite sure what you want to achieve above, but calling super might come in handy somewhere to resume the default Devise behaviour

Check if user is active before allowing user to sign in with devise (rails)

I am using devise and created a User field called :active which is either true or false. I have to manually make the user active (true) before the user is allowed to log in. At least that is the intent. I have tried this...
class SessionsController < Devise::SessionsController
# POST /resource/sign_in
def create
"resource/signin CREATE"
self.resource = warden.authenticate!(auth_options)
unless resource.active?
sign_out
redirect_to :sorry_not_active_url
return
end
set_flash_message(:notice, :signed_in) if is_navigational_format?
sign_in(resource_name, resource)
respond_with resource, :location => after_sign_in_path_for(resource)
end
end
However this does not catch all the places where a user can log in, for example, when a user changes their password, the site automatically logs them in automatically after. However, if the user is not active, I do not want them to be allowed to log in, but rather be redirected to a sorry_not_active_url.
What would be the best way to prevent the user from signing in if the user is not active?
Thank you.
Add these two methods to your user model, devise should pick them up automatically - you should NOT need to extend Devise::SessionsController
def active_for_authentication?
super && self.your_method_for_checking_active # i.e. super && self.is_active
end
def inactive_message
"Sorry, this account has been deactivated."
end
Devise (If you have devise 3.2+) now support block parameter in (session) create
# assuming this is your session controller
class SessionsController < Devise::SessionsController
def create
super do |resource|
unless resource.active?
sign_out
# you can set flash message as well.
redirect_to :sorry_not_active_url
return
end
end
end

How to make Devise redirect after confirmation

How can I create an after-confirmation redirect in Devise?
Before I added the confirmation module the custom after_sign_up_path worked fine for the first time login/signup but now when I click the confirmation link in the email it redirects to the path I set for the after-login path (user profile).
My goal is to create a form wizard and "getting started" page to collect additional information. The obvious caveat being that this redirect will only happen one time, upon confirmation.
I tried some other solutions that have been posted on Stack Overflow but none of them seem to work any longer.
A less intrusive way of achieving this might be just overriding the after_confirmation_path_for method of Devise::ConfirmationsController.
Create a new confirmations_controller.rb in app/controllers directory:
class ConfirmationsController < Devise::ConfirmationsController
private
def after_confirmation_path_for(resource_name, resource)
your_new_after_confirmation_path
end
end
In config/routes.rb, add this line so that Devise will use your custom ConfirmationsController. This assumes Devise operates on users table (you may edit to match yours).
devise_for :users, controllers: { confirmations: 'confirmations' }
Restart the web server, and you should have it.
Essentially, you want to change around line 25 of Devise's ConfirmationsController.
This means you need to override the show action modifying the "happy path" of that if statement in the show action to your heart's content:
class ConfirmationsController < Devise::ConfirmationsController
def new
super
end
def create
super
end
def show
self.resource = resource_class.confirm_by_token(params[:confirmation_token])
if resource.errors.empty?
set_flash_message(:notice, :confirmed) if is_navigational_format?
sign_in(resource_name, resource)
respond_with_navigational(resource){ redirect_to confirmation_getting_started_path }
else
respond_with_navigational(resource.errors, :status => :unprocessable_entity){ render_with_scope :new }
end
end
end
And a scoped route for it (I put the view and action in the registrations controller but you can change it to whatever):
devise_for :users, controllers: { confirmations: 'confirmations' }
devise_scope :user do
get '/confirmation-getting-started' => 'registrations#getting_started', as: 'confirmation_getting_started'
end
The default show action is referring to the protected after_confirmation_path_for method, so as another option, you could just modify what that method returns.
Have you checked the Devise wiki? It explains how to do this, with the after_signup_path_for being the path to define in your case.
From the wiki:
Make a new controller "registrations_controller.rb" and customize the appropriate method:
class RegistrationsController < Devise::RegistrationsController
protected
def after_sign_up_path_for(resource)
'/an/example/path'
end
end
Then add a route to use it:
Modify config/routes.rb to use the new controller
devise_for :users, :controllers => { :registrations => "registrations" }
The solution given by #Lee Smith is working perfectly but I wish to add a little addition: We don't need to add the new and create actions while overriding the Devise confirmations controller for this case:
class ConfirmationsController < Devise::ConfirmationsController
def show
self.resource = resource_class.confirm_by_token(params[:confirmation_token])
if resource.errors.empty?
set_flash_message(:notice, :confirmed) if is_navigational_format?
sign_in(resource_name, resource)
respond_with_navigational(resource){ redirect_to your_desired_redirect_path }
else
respond_with_navigational(resource.errors, status: :unprocessable_entity){ render_with_scope :new }
end
end
end
Then in the route file, just add routing for the confirmations controller.
devise_for :users, controllers: { confirmations: "confirmations" }
I just went through all of this and none of the other answers worked (2015-04-09 with devise 3.4.1).
After signup, I wanted the user to be redirected to the login page with a message about a confirmation email. To get that working, here's what I had to do:
class RegistrationsController < Devise::RegistrationsController
protected
# This is the method that is needed to control the after_sign_up_path
# when using confirmable.
def after_inactive_sign_up_path_for(resource)
new_user_session_path
end
end
I just found this comment which would have sent me exactly where I needed to be much sooner.
Here is the reference to the after_inactive_sign_up_path_for that
mentions Niels: Devise wiki – marrossa Nov 13 '12 at 3:38
The confirmation_path also must be configured while working with refinerycms integrated in a rails app

Devise authentication - no confirmation after password recovery

I'm using Devise authentication gem with Rails.
How to display the message from devise.en.yml:
send_instructions: 'You will receive an email with instructions about how to reset your password in a few minutes'
after password recovery e-mail has been sent, instead of being redirected to site's root?
Update:
I've found an interesting piece of code in devise_controller.rb:
def successfully_sent?(resource)
notice = if Devise.paranoid
resource.errors.clear
:send_paranoid_instructions
elsif resource.errors.empty?
:send_instructions
end
if notice
set_flash_message :notice, notice if is_navigational_format?
true
end
end
Setting breakpoints shows that the right lines are being called, :send_instructions is assigned to notice , set_flash_message is called, but I cannot see the result of all this because I am immediately redirected to root path.
Look at the source code for devise's PasswordsController: https://github.com/plataformatec/devise/blob/master/app/controllers/devise/passwords_controller.rb#L42
You'll have to create a PasswordsController in your app that inherits from Devise::PasswordsController, implement only the after_sending_reset_password_instructions_path_for(resource_name) method and when setting the routes tell devise to use your controller
class PasswordsController < Devise::PasswordsController
protected
def after_sending_reset_password_instructions_path_for(resource_name)
#return your path
end
end
in routes
devise_for :users, :controllers => { :passwords => "passwords" }

How to "soft delete" user with Devise

I currently use Devise for user registration/authentication in a Rails project. When a user wants to cancel their account, the user object is destroyed, which leaves my application in an undesired state.
What is the easiest way to implement a "soft delete", i.e. only removing personal data and marking the user as deleted? I still want to keep all record associations.
I assume I will have to first introduce a new "deleted" column for users. But then I am stuck with this default code in the user's profile view:
<p>Unhappy? <%= link_to "Cancel my account", registration_path(resource_name), :confirm => "Are you sure?", :method => :delete %>.</p>
Where can I find the :delete method? How should I overwrite the default Devise methods?
I could advise overriding destroy method on your User model to simply do update_attribute(:deleted_at, Time.current) (instead of actually destroying), but this deviation from standard API could become burdensome in the future, so here's how to modify the controller.
Devise has a bunch of default controllers out of the box. The best way to customize them is to create your own controller inheriting the corresponding devise controller. In this case we are talking about Devise::RegistrationsController — which is easily recognized by looking at source. So create a new controller.
class RegistrationsController < Devise::RegistrationsController
end
Now we have our own controller fully inheriting all the devise-provided logic. Next step is to tell devise to use it instead of the default one. In your routes you have devise_for line. It should be changed to include registrations controller.
devise_for :users, :controllers => { :registrations => 'registrations' }
This seems strange, but it makes sense because by default it's 'devise/registrations', not simply 'registrations'.
Next step is to override the destroy action in registrations controller. When you use registration_path(:user), :method => :delete — that's where it links. To destroy action of registrations controller.
Currently devise does the following.
def destroy
resource.destroy
set_flash_message :notice, :destroyed
sign_out_and_redirect(self.resource)
end
We can instead use this code. First let's add new method to User model.
class User < ActiveRecord::Base
def soft_delete
# assuming you have deleted_at column added already
update_attribute(:deleted_at, Time.current)
end
end
# Use this for Devise 2.1.0 and newer versions
class RegistrationsController < Devise::RegistrationsController
def destroy
resource.soft_delete
Devise.sign_out_all_scopes ? sign_out : sign_out(resource_name)
set_flash_message :notice, :destroyed if is_navigational_format?
respond_with_navigational(resource){ redirect_to after_sign_out_path_for(resource_name) }
end
end
# Use this for older Devise versions
class RegistrationsController < Devise::RegistrationsController
def destroy
resource.soft_delete
set_flash_message :notice, :destroyed
sign_out_and_redirect(resource)
end
end
Now you should be all set. Use scopes to filter out deleted users.
Adding onto hakunin's answer:
To prevent "soft deleted" users from signing in, override active_for_authentication? on your User model:
def active_for_authentication?
super && !deleted_at
end
You could use acts_as_paranoid for your User model, which sets a deleted_at instead of deleting the object.
A complete tutorial can be found at Soft Delete a Devise User Account on the Devise wiki page.
Summary:
1. Add a "deleted_at" DATETIME column
2. Override users/registrations#destroy in your routes
3. Override users/registrations#destroy in the registrations controller
4. Update user model with a soft_delete & check if user is active on authentication
5. Add a custom delete message
def devise_current_user
#current_user ||= warden.authenticate(scope: :user)
end
def current_user
if params[:user_id].blank?
devise_current_user
else
User.find(params[:user_id])
end
end

Resources