How can I customize error messages to override devise passwords controller?
class PasswordsController < Devise::PasswordsController
def create
self.resource = resource_class.send_reset_password_instructions(params[resource_name])
if resource.errors.empty?
set_flash_message(:notice, :send_instructions) if is_navigational_format?
respond_with resource, :location => home_path
else
binding.pry
flash[:devise_password_error] = (resource.errors.map do |key, value|
value.capitalize
end).flatten.join('|')
redirect_to home_path and return
end
end
def edit
self.resource = resource_class.new
resource.reset_password_token = params[:reset_password_token]
end
end
resource.errors is available in this method but it contains default messages such as Email not found and Email can't be blank. i need to customize this messages. I've tried to remove :validatable from my user model and add custom validators but this works only for my custom registrations controller derived from Devise::RegistrationsController and not for custom passwords controller.
Is there any solution?
The answer is to modify config/locales/devise.en.yml but you must add the settings, they are not there by default.
en:
activerecord:
errors:
models:
user:
attributes:
password:
confirmation: "does not match"
too_short: "is too short (minimum is %{count} characters)"
Credit for this goes to Vimsha who answered virtually the same question for me.
Devise messages are located in config/locales/devise.en.yml
I'm not sure which message you're trying to override, but that's where you want to do that.
It's not ideal, but based on this related ticket I've got it working with the following (which I know is a bit of a hack, but it works):
module DeviseHelper
def devise_error_messages!
resource.errors.full_messages.map { |msg| msg == 'Email not found' ? 'The email address you entered could not be found. Please try again with other information.' : msg }.join('<br/>')
end
end
Put this in a module called devise_helper.rb in your /app/helpers directory
Add this to your routes.rb
devise_for :users, controllers: { passwords: 'passwords' }
or
devise_for :users, :controllers => { :passwords => 'passwords' }
Related
I have set confirmable in my devise user model but when I try to register email, which is already registered, I get error that email is registered or waiting for confirmation.
This is correct but for security reasons I want to always show something like "confirmation email has been sent to email address" so nobody can figure out which emails are already registered in the app.
Is this somewhere in devise config? Or do I have to manually modify registration controller to not throw any error if email exists in db?
The best way is way is to manually modify from registration controller (https://github.com/heartcombo/devise#configuring-controllers), you can rescue the error and changes the flash message.
I borrowed has_duplicate_email? from this post , checked devise source code and then modified create in registration controller and routes to use it. I hope it helps somebody
def has_duplicate_email?
return false unless resource.errors.has_key?(:email)
resource.errors.details[:email].any? do |hash|
hash[:error] == :taken
end
end
def create
super do |resource|
if has_duplicate_email?
set_flash_message! :notice, :"signed_up_but_#{resource.inactive_message}"
expire_data_after_sign_in!
redirect_to root_path and return
end
end
end
devise_for :users, :controllers => {:registrations => "users/registrations"}
I am trying to override Devise's error message 'reset_password_token is invalid' entirely. I would like it to read "password reset link has already been used." How can I do this? There does not seen to be a field or keyword for this in devise.en.yml.
A simpler solution than overwriting the passwords_controller, is simply to modify the view:
In app/views/devise/passwords/edit.html.haml (or your erb equivalent),
Just put this conditional inside the form:
- if resource.errors[:reset_password_token].present?
.alert.alert-danger
This password reset URL has expired. You may have requested to reset your password more than once. Follow the link in the most recent email or
= link_to 'request to reset your password again.', new_user_password_path
And you may want to remove these two lines:
= f.error_notification
= f.full_error :reset_password_token
Reset password token is invalid message is a validation error thrown while updating password, and is not a devise specific error ( for which the messages stored in devise.en.yml).
This validation happens in the devise/passwords_controller#update method.
Code included below:
# PUT /resource/password
def update
self.resource = resource_class.reset_password_by_token(resource_params)
yield resource if block_given?
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_flashing_format?
sign_in(resource_name, resource)
respond_with resource, location: after_resetting_password_path_for(resource)
else
respond_with resource
end
end
The self.resource = resource_class.reset_password_by_token(resource_params) line sets the resource.errors object with the error message related to reset_password_token being invalid.
Inspecting the value of resource.errors after this line will show a big hash ending with ... #messages={:reset_password_token=>["is invalid"]}
The devise_error_messages method reformats this to say "Reset Password Token is invalid".
To change this message, the passwords controller should be customized and the update method changed to have a different error message hash.
Steps would be as follows:
1) Customize the routes for passwords controller
# config/routes.rb
devise_for :users, :controllers => { :passwords => "passwords" }
2) Create the customized passwords controller
# app/controllers/passwords_controller.rb
class PasswordsController < Devise::PasswordsController
end
3) Customize the update method to change the error message:
# app/controllers/passwords_controller.rb
# Include the update method as it is fully, with the following changes in the else block:
def update
...
if resource.errors.empty?
...
else
if resource.errors.messages[:reset_password_token]
resource.errors.messages.delete(:reset_password_token)
resource.errors.messages[:password_reset_link] = ["has already been used"]
end
respond_with resource
end
More about Customizing Devise controllers
In order to send new confirmation instructions, an email has to be entered. I want to avoid that because my users are logged in at that moment, so there's no need for email asking. I just want to send new instructions to the current_user.email
I don't want to do client side stuff like this:
= f.email_field :email, value: current_user.email, class: "hidden"
I need a server side solution.
Thanks guys!
As per the devise codebase, sending confirmation email can be invoked on a user as follows:
user = User.find(1)
user.send_confirmation_instructions
So you don't really need to get an email from the form.
You have access to device method , this should work.
See the documentation here
routes.rb
devise_for :users, controllers: { confirmations: "confirmations" }
In view
= link_to "resend confirmation", user_confirmation_path, data: { method: :post }
I ended up with this:
First, override devise controller:
config/routes.rb
devise_for :users, controllers: { confirmations: "users/confirmations" }
controllers/users/confirmations_controller.rb
class Users::ConfirmationsController < Devise::ConfirmationsController
def create
redirect_to new_user_session_path unless user_signed_in?
if current_user.confirmed?
redirect_to root_path
else
current_user.send_confirmation_instructions
redirect_to after_resending_confirmation_instructions_path_for(:user)
end
end
end
protected
# The path used after resending confirmation instructions.
def after_resending_confirmation_instructions_path_for(resource_name)
flash[:notice] = "Instructions sent successfully."
is_navigational_format? ? root_path (or whatever route) : '/'
end
end
Then remove the email field from the view.
views/devise/confirmations/new.html.haml
= form_for(resource, as: resource_name, url: confirmation_path(resource_name), method: :post }) do |f|
= f.submit "Resend confirmation instructions"
Thanks everyone for your answers.
I have a custom mailer (UserMailer.rb) and a few methods to override the default Devise methods for the welcome email and forgot password emails. The mailer uses a custom template to style the emails--and it works great.
In config/initializers, I have a file with
module Devise::Models::Confirmable
# Override Devise's own method. This one is called only on user creation, not on subsequent address modifications.
def send_on_create_confirmation_instructions
UserMailer.welcome_email(self).deliver
end
...
end
(Again, UserMailer is setup and works great for the welcome email and reset password email.)
But what's not working is the option to "Resend confirmation instructions." It sends with the default Devise styling and I want it to use the styling of my mailer layout. I know I can manually add the layout to the default Devise layout, but I'd like to keep DRY in effect and not have to do that.
I've tried overriding the send_confirmation_instructions method found here, but I'm getting a wrong number of arguments (1 for 0) error in create(gem) devise-2.2.3/app/controllers/devise/confirmations_controller.rb at
7 # POST /resource/confirmation
8 def create
9 self.resource = resource_class.send_confirmation_instructions(resource_params)
In my initializer file, I'm able to get to this error by adding a new override for Devise, but I'm probably not doing this correctly:
module Devise::Models::Confirmable::ClassMethods
def send_confirmation_instructions
UserMailer.send_confirmation_instructions(self).deliver
end
end
Any ideas?
You don't have to go through that initializer to do that. I've done this by overriding the confirmations controller. My routes for devise look like:
devise_for :user, :path => '', :path_names => { :sign_in => 'login', :sign_out => 'logout', :sign_up => 'signup'},
:controllers => {
:sessions => "sessions",
:registrations => "registrations",
:confirmations => "confirmations"
}
Then, create the confirmations_controller and extend the Devise::ConfirmationsController to override:
class ConfirmationsController < Devise::ConfirmationsController
In that controller, I have a create method to override the default:
def create
#user = User.where(:email => params[:user][:email]).first
if #user && #user.confirmed_at.nil?
UserMailer.confirmation_instructions(#user).deliver
flash[:notice] = "Set a notice if you want"
redirect_to root_url
else
# ... error messaging or actions here
end
end
Obviously, in UserMailer you can specify the html/text templates that will be used to display the confirmation message. confirmation_token should be a part of the #user model, you can use that to create the URL with the correct token:
<%= link_to 'Confirm your account', confirmation_url(#user, :confirmation_token => #user.confirmation_token) %>
I'm setting up Devise such that users can log in and use the site without having confirmed their email address, similar to this question. But there are a couple of features on the site that users can't use unless they've confirmed.
OK, that's fine. I can check for current_user.confirmed?. If they're not confirmed, I can put a button on the page to have them request the confirmation be sent again.
The issue I'm having is that when they do this while logged in, the flash message they see on the result page is "You are already signed in." Which isn't ideal - I just want to put up the message that the confirmation was sent.
I'm starting down the path of trying to figure out which method of the Devise::ConfirmationController to override, and to what, but I'm hoping someone has done this already.
The reason the flash says "You are already signed in" is because the user is being redirected to new_session_path from the after_resending_confirmation_instructions_path_for method. I would override this method to check if they are logged in. If they are, then don't redirect to new_session_path, set your flash message and redirect to another page.
Override the confirmations controller by putting it in controllers/users/confirmations_controller.rb
class Users::ConfirmationsController < Devise::ConfirmationsController
protected
def after_resending_confirmation_instructions_path_for(resource_name)
if signed_in?
flash[:notice] = "New message here" #this is optional since devise already sets the flash message
root_path
else
new_session_path(resource_name)
end
end
end
Add your confirmationsController to routes->
devise_for :users, :controllers => {:confirmations => 'users/confirmations' }
I think it should look something like this:
module Devise
module ConfirmationsController
extend ActiveSupport::Concern
included do
alias_method_chain :show, :new_flash
end
def show_with_new_flash
# do some stuff
flash[:notice] = "New message goes here"
end
end
end
Could edit
config/locales/devise.en.yml to be more relavent at line:
failure:
already_authenticated: 'You are already signed in.'
or you could do this in your view where flash message has been added
<%=content_tag :div, msg, id: "flash_#{name}" unless msg.blank? or msg == "You are already signed in."%>
I'm using Devise 3.1.0, there's a different method for this scenario instead of after_resending_confirmation_instructions_path_for described in the top voted answer. I modified mine like so:
class Users::ConfirmationsController < Devise::ConfirmationsController
protected
def after_confirmation_path_for(resource_name, resource)
if signed_in?
set_flash_message(:notice, :confirmed)
root_path
elsif Devise.allow_insecure_sign_in_after_confirmation
after_sign_in_path_for(resource)
else
new_session_path(resource_name)
end
end
end