Devise how to change reset_password_token error - ruby-on-rails

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

Related

how to hide info that email is already registered with devise rails

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"}

Devise (1.0.7) authentication fails in rails 2.3.5

Tried to use Devise 1.0.6 in a Rails 2.3.5 project (I know it is bit old version). I have used instructions here to setup devise. Sign up is working fine but when sign in, the password field is cleared up and nothing happened. There is no error showing in log. admin_signed_in? gives not signed in.
when sign in, controller create action as follows
def create
if resource = authenticate(resource_name)
set_flash_message :notice, :signed_in
sign_in_and_redirect(resource_name, resource, true)
elsif [:custom, :redirect].include?(warden.result)
throw :warden, :scope => resource_name
else
set_now_flash_message :alert, (warden.message || :invalid)
clean_up_passwords(build_resource)
render_with_scope :new
end
end
authenticate() method in model as follows
def authenticate(attributes={})
return unless authentication_keys.all? { |k| attributes[k].present? }
conditions = attributes.slice(*authentication_keys)
resource = find_for_authentication(conditions)
resource if resource.try(:valid_for_authentication?, attributes)
end
inspected resource in model and it returns a valid record. but its not reaching to controller. in controller authenticate(resource_name) returns nil
helper.rb
def authenticate(scope)
warden.authenticate(:scope => scope)
end
scope is valid and warden.authenticate(:scope => scope) returns null .

How can I configure my app to allow only specific emails to register with devise?

I'm setting up a CRUD feature and I only want a few people (entire email addresses) to be able to register through devise.
I've gone through countless posts but they mostly use email domains or single person login. I've also tried creating my own validation method in my User.rb but I can't seem to get it.
validate :check_email
private
def check_email
#users = User.all
if #users == '123.example#gmail.com')
events_path
else
errors.add(:email, 'is not authorized')
end
end
I don't get an explicit error but the app seems to skip my 'if' condition and outputs the 'else' condition.
To allow only users with certain email addresses you could simply add an inclusion validation.
class User < ApplicationRecord
ALLOWED_EMAILS = %w[
123.example#gmail.com
456.example#gmail.com
789.example#gmail.com
].freeze
validates :email, inclusion: { in: ALLOWED_EMAILS, message: :invalid }
# ...
end
You could also opt to load the ALLOWED_EMAILS from the settings or a file.
To load from the Rails config you have to define the email addresses in a config file.
config.allowed_user_emails = %w[
123.example#gmail.com
456.example#gmail.com
789.example#gmail.com
]
Then load them in the controller using:
ALLOWED_EMAILS = Rails.configuration.allowed_user_emails.freeze
To load from for example a yaml file you could do something like:
ALLOWED_EMAILS = YAML.load_file(Rails.root.join('config', 'allowed_user_emails.yml')).freeze
Having the following in the file:
- 123.example#gmail.com
- 456.example#gmail.com
- 789.example#gmail.com
The easiest way to accomplish this would be to create a custom validation like you did originally. In your user model you could create a validation that looks something like this:
validate :is_email_valid?
def is_email_valid
if ["example#gmail.com", "example#yahoo.com", "123.example#gmail.com"].include?(self.email)
errors.add :base, "Your email is not authorized for use!"
end
end
This code will work when saving or creating a new record.
The problem with your code is that you are attempting to validate on a ActiveRecord_Relation object which cannot directly access validations for class instances. It's like trying to call an instance method from a class level; you have to validate one user at a time. You will want to execute validations on your object instance with a reference to self. So, looping through your users and then validating would work. Here is an example:
User.all.each do |i|
if i.valid?
puts "VALID"
else
puts "INVALID"
end
end
Sarah,
The first step that I'd take in accomplishing this would be to work inside devise's registrations_controller. This is the controller where the signed up user will be served to and here you will want to over-write some of the Devise code.
Ensure the controllers are created first by running Devise's controller generator:
rails generate devise:controllers users
Then you'll want to find the user's registrations_controller.rb and override the code in there under the create action.
Here's an example of some code I wrote to override Devise's admin controller:
def create
build_resource(sign_up_params)
# Below - If admin is coming from an email invite or shared invite link, grabs both token and workplace id from params.
#token = params[:invite_token] if params[:invite_token]
#workplace_id = params[:workplace_id] if params[:workplace_id]
#workplace = Workplace.find(params[:workplace_id]) if params[:workplace_id] # Finds the workplace from the workplace_id, works for both invite email and shared link.
#institute = #workplace.institute if params[:workplace_id]
if #institute && #institute.has_super_admins?
resource.super_admin = false
end
if resource.save
yield resource if block_given?
if resource.persisted?
if resource.active_for_authentication?
# Below - If admin came from a shared workpalce invite link or email workplace invite
if #token != nil # Admin signed up via a workplace invite email or the shared link
# Below - Checks Payment plan for admins joining an institute to make sure the institute didn't exceed user count subscription permission.
unless #institute.plan.unlimited_users?
if #institute.plan.user_count <= #institute.admins.count # Admin doesn't join workplace since institute has excited user allowance
set_flash_message! :alert, :signed_up_no_workplace, :workplace => #workplace.name, :institute => #institute.name, :workplace_owner => #institute.subscription.admin.name
sign_up(resource_name, resource)
respond_with resource, location: institute_admin_path(resource.institute, resource)
else # Admin successfully signs up and joins the workplace. Method is below in protected
join_workplace_and_redirect
end
else # Admin successfully signs up and joins the workplace. Method is below in protected
join_workplace_and_redirect
end
else # Fresh admin signup
sign_up(resource_name, resource)
if resource.super_admin? # Checks if the admin is a super_admin and set as true, if so redirects to another page
set_flash_message! :notice, :super_admin_signed_up, :name => resource.first_name
respond_with resource, location: new_institute_path()
else # Admin is not a super_admin and not first one who signed up inside a city.
set_flash_message! :notice, :admin_signed_up, :link => edit_institute_admin_path(resource.institute, resource), :city => resource.institute.name
respond_with resource, location: after_sign_up_path_for(resource)
end
end
else
set_flash_message! :notice, :"signed_up_but_#{resource.inactive_message}"
expire_data_after_sign_in!
respond_with resource, location: after_inactive_sign_up_path_for(resource)
end
else
flash[:alert] = "Your profile could not be created. See why below!"
set_flash_message! :alert, :"signed_up_but_#{resource.inactive_message}"
redirect_to request.referrer
end
else # Failed to save
clean_up_passwords resource
respond_with resource, location: new_admin_registration_path(workplace: #workplace)
end
end
To apply it to your case, what you may want to do is use a case statement to see if the email matches ones you want it to. For example, below you'll want to check the resource (the single user in this case, the user who is signing up) email attribute to determine whether it's a success or failure:
# Checks emails that are allowed
case resource.email
when "123.example#gmail.com"
if resource.save # Success
set_flash_message! :notice, :signup_succeed
respond_with resource, location: home_page(resource)
else # For some reason the allowed email didn't go through due to other validations
set_flash_message! :alert, :signup_failure
respond_with resource, location: new_user_path(resource)
end
else # Entered an email that is not allowed
set_flash_message! :alert, :email_invalid
respond_with resource, location: new_user_path(resource)
end
The set_flash_message! is Devises custom messages which can be edited in config/locales/devise.en.yaml. The second keyword refers to the name of the yaml key, and you can customize the error or success message in there. respond_with is the redirection and location. You can use as many when statements as needed. This is just one way to do it.
Hope this helps.

BCrypt::Errors::InvalidHash in SessionsController#create when I try to sign in a user? (Ruby on Rails)

I get this error when I try to sign in a user, and can't figure out why. It's weird because when I run the following code I get the BCrypt Error, however when I change the find_by line (line 7) from can_email (candidate's email) to can_name (candidate's first name) I don't get the error at all, it just doesn't sign in the user presenting an "invalid password/email combination" error message on the webpage regardless if the combination is right or not. It's something to do with the password but I can't pin point it.
class SessionsController < ApplicationController
def new
end
def create
candidate = Candidate.find_by_can_email(params[:can_email])
if candidate && candidate.authenticate(params[:password]) **Error highlights this line**
session[:candidate_id] = candidate.id
redirect_to candidate
else
flash.now[:error]="Invalid email/password combination"
render 'new'
end
end
def destroy
if signed_in?
session[:candidate_id] = nil
else
flash[:notice] = "You need to log in first"
end
redirect_to login_path
end
end
Having the SessionController i am assuming you have a route as follows
# This is just a sample
post 'login' => "sessions#create" # gives login_path
Since there will be no session model i assume you have the form as follows
form_for(:session, url: login_path)
Now if you are collecting eg can_email and password you get
{session: {password: 'foo', can_email: 'foo#bar.com'}}
Accessing params[:session] returns the hash containing email and passowrd
So i think you should obtain them as follows
def create
candidate = Candidate.find_by(can_email: params[:session][:can_email])
if candidate && candidate.authenticate(params[:session][:password])
# login the user
else
# whatever
end
end
I got this error too, but in my case it was the result of myself having changed the encrypted_password value of my user in the database a while back and then forgetting about it.
This was easily fixed just by updating the password :)

Devise: How to override devise error messages on password change

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' }

Resources