Preventing Devise from sending an email when changing password - ruby-on-rails

I am using Devise with my Rails 3 application. The current behavior for resetting a password, is to click on the "Forgot your password?" link. The link here is:
(url)/password/new.user
Which will call the following method in the Devise passwords_controller.rb:
def new
build_resource({})
end
This method will do:
generates the password reset token and adds it to the database,
sends an email to the person with a link that includes the token:
(url)/password/edit?reset_password_token=xxxxxxxxxxxxxxx
Is there any way to convince Devise to perform step 1 ONLY and not step 2? Are there any security issues I should be aware of if this is possible, and I did take this approach in an effort to simplify a portion of the web site.

I would recommend overriding send_devise_notification on your User (?) model and return true when the notification value is :reset_password_instructions. Something like this:
# app/models/user.rb
def send_devise_notification(notification)
return true if notification == :reset_password_instructions
end
Check their example on how to override/customize behavior for sending emails
https://github.com/plataformatec/devise/blob/master/lib/devise/models/authenticatable.rb#L127

You can disable it at instance level:
# disable all notifications
user.define_singleton_method(:send_devise_notification) { |*_| true }
# disable the one you want
user.define_singleton_method(:send_devise_notification) do |*args|
return true if args[0] == :reset_password_instructions
super
end

The title of the question is general, but the question itself is more specific. This is the answer to the general question as of 2021.
To prevent a password changed email notification from being sent when changing a user password, call skip_password_change_notification! on the user before saving the user.
user = User.find(123)
user.skip_password_change_notification!
user.password = 'DoNotUse$123'
user.save

Related

Devise - how to get user when redirecting with a custom Devise::FailureApp

I let users log in initially without confirming their email address - but after 7 days, if they haven't confirmed - I block access until they confirm their address.
(Note - this is achieved by setting config.allow_unconfirmed_access_for = 7.days in the Devise initialiser)
If they hit the 'grace' limit (e.g. they don't confirm and 7 days pass) then I want to:
send them to a page which explains what is going on (I can do this
part)
automatically re-send the confirmation email
to do #2 I need to access the user to get the email address.
Devise obviously 'knows' who the user is - that's how it knows they have passed the confirmation expiry.
If the user has just tried to log in, then I can get this by looking in the params.
However if the user already has a live login token in their session, then when they pass the magical week - they'll suddenly start being rejected by devise. How do I access the user in this case?
#based on
#https://github.com/plataformatec/devise/wiki/How-To:-Redirect-to-a-specific-page-when-the-user-can-not-be-authenticated
#https://stackoverflow.com/questions/9223555/devise-with-confirmable-redirect-user-to-a-custom-page-when-users-tries-to-sig
class CustomFailure < Devise::FailureApp
def redirect_url
if warden_message == :unconfirmed
user = User.find_by_email(params.dig(:user,:email))
user&.send_confirmation_instructions
if user.nil?
#if the user had a valid login session when they passed the grace period, they will end up here
!! how do I get the user in this scenario !!
end
confirmation_required_info_path(params: {found: !user.nil?})
elsif warden_message == :invalid
new_user_session_path(user:{email: params.dig(:user,:email)})
else
super
end
end
# You need to override respond to eliminate recall
def respond
if http_auth?
http_auth
else
redirect
end
end
end
This achieves goal #1, but it only achieves goal #2 if if the failure is the result of new signup
is there a direct way to access the user when they have a live session, but have passed the expiry date?
(current_user is not available, env['warden'].user is nil)
thank you
Rails 5.0.6
devise 4.2
edit: Updating to clarify with an example scenario where I need help:
day 0: User signs up with email/password. I let them in without confirming their email. They have a 7-day grace period to confirm their email.
day 2: They log out
day 7 (morning): They log in again
day 7 (later in the day): They do some action. Their login token is still valid - devise recognises it, finds their user record and checks if they have confirmed their email address. They have not - so devise refuses to authorise the action, giving the error :unconfirmed
In this scenario - they come through to the failure app. I will redirect them to a page which says 'you have passed your 7-day grace period, you really need to confirm your email address now'.
In the failure app, I want to know what their email address is so that I can automatically re-send the confirmation email. How do I get this?
Note - in this scenario, devise has refused authorisation. current_user is nil. However Devise clearly 'knows' who the user is - because it was able to look up their record in the database, and check that they had gone past the grace period for unconfirmed email addresses. How do I access that same 'knowledge'
I think there are better ways of achieving the same result without creating a Devise::FailureApp:
This could be achieved by overriding the confirmed? method from Devise's resource extension present in the Confirmable module.
A simple example would be:
Add a delayed_confirmation_expiry_date datetime field to your model's table, via migration.
This field will be used to store the expiry datetime when the user first registers into your app. You will have to override the SessionsController#create method for that, so it can call the #delay_confirmation! method on your resource.
Add inside your User model equivalent :
# Will update the field you have added with the new temporary expiration access datetime
def delay_confirmation!(expiry_datetime=7.days.from_now)
self.delayed_confirmation_expiry_date = expiry_datetime
self.save
end
# Override that will make sure that, once the user is confirmed, the delayed confirmation information is cleared
def confirm(args={})
clear_delay_confirmation!
super
end
# Self-explanatory
def clear_delay_confirmation!
self.delayed_confirmation_expiry_date = nil
self.save
end
# Used on your controllers to show messages to the user warning him about the presence of the confirmation delay
def confirmation_is_delayed?
self.confirmed? && !self.confirmed_at.present?
end
# Overrides the default implementation to allow temporary access for users who haven't confirmed their accounts within the time limit
def confirmed?
if !self.confirmation_is_delayed?
super
else
self.delayed_confirmation_expiry_date >= DateTime.now.in_time_zone
end
end

Devise: Is it possible to NOT send a confirmation email in specific cases ?

Actually I am using devise for login and registration and its working fine, it send a confirmation email which is really good. But I need to send confirmation email in specific cases. I am passing user type in URL and on behalf of that I want to set mail. I have two type of users, one will be confirm their account their self reset of the users can not confirm their account only admin can approve their accounts. I have override the create method
def create
super
if params[:type]=='xyz
#user.skip_confirmation_notification!
end
end
but it sends mail in both cases. Please tell where am wrong.
So according to devise confirmable module you can skip to send confirmation and email by following code.
def confirm_your_type_user_without_confirmation_email
# check your condition here and process the following
self.skip_confirmation!
self.confirm!
# condition may end here
end
Now lets call it on create hook.
class User < ActiveRecord::Base
after_create :confirm_your_type_user_without_confirmation_email
....
end
for more reference you may check this:
Devise Confirmable module
The solution should be something similar as I mentioned here above. And its best practice to avoid controller to handle these responsibilities, because its not something your controller should take. :)
I hope my answer will give you some way to solve your problems! Thanks!

Devise: how to use another mail view when user requests email change ( instead of the default )

Logged in user changes email under users#registration
User receives email to confirm this email change
The email send in step 2 is exactly the same as when a user registers for the first time and has to confirm his email for the first time.
Is there a way to use another email view So I can more customise the text like "you requested your email to be changed, please click below link.. " instead of using the default message.
This would be a cosmetic but much wanted feature in our application.
Anyway to do this? Kind regards!
This is not trivial task, actualy you need to overwrite this devise method inside your model:
def send_confirmation_instructions
if reconfirmation_required?
self.confirmation_token = nil if reconfirmation_required?
#reconfirmation_required = false
ensure_confirmation_token!
opts = pending_reconfirmation? ? { :to => unconfirmed_email } : { }
send_devise_notification(:reconfirmation_instructions, opts)
else
super
end
end
Then you need to extend Devise::Mailer to add method reconfirmation_instructions
And finally you need to create view app/views/devise/mailer/reconfirmation_instructions.html.erb

how to set email_required = false on signup with omniauth-twitter

In a Rails 3.2 app I'm using Devise on a User model, with omniauth-twitter. A User has many Authentications.
Login is via email + password, or via Twitter.
I'm trying to modify the email_required method so that email is not required if a User is signing up with Twitter (Twitter does not provide email in its email response). The User will then be taken to a settings screen where he will need to set his email.
I'm having trouble working out what my condition should be here. I'm thinking something like
def email_required?
if user.sign_in_count == 1 && self.authentications.first.provider == "twitter"
false
else
super
end
end
but (1) the Authentication record is built but not created when this validation is passed, so not available; and (2) I want email to be required immediately after the User is created (i.e., the signin count may still be 1)
Another option may be to check if Twitter params are in the session, something like (pseudocode)
def email_required?
if session["devise.twitter_data"]
false
else
super
end
end
but accessing the session from a model doesn't seem right.
Would appreciate any suggestions or thoughts, I have a feeling I'm overlooking something obvious!

Rails 3 with Devise for Authentication - How do I manually create a user?

I would like to manually create new Users, without forcing them to verify their email address.
The idea is to allow existing users to automatically add their friends without requiring their registration. It makes sense for the business case I'm working to solve.
How can this be achieved with Devise?
The skip_confirmation! method is available to any confirmable model.
#user = User.new params[:user]
#user.skip_confirmation! # Sets confirmed_at to Time.now, activating the account
#user.save
The user account will be activated though. If you don't want that, continue reading.
Devise uses conditional callbacks to generate the confirmation token and send the email. The callbacks will be called only if confirmation_required? returns true. Redefine it on your model:
def confirmation_required?
false
end
However, this will make the active_for_authentication? method always return true because it takes whether or not confirmation is required into account. We have to redefine that as well:
def active_for_authentication?
confirmed? || confirmation_period_valid?
end
This way, the account will stay inactive and no confirmation email will be sent. You will have to manually activate the user by calling confirm! on the record or just setting confirmed_at to any date.
It's quite a hack, but it should work.
For reference: confirmable.rb
I just want to add for future reference that since Devise 2.2 there is now a skip_confirmation_notification! method available as well which basically does everything from Matheus' post without redefining the methods in the model.

Resources