Devise / ActionMailer sending duplicate emails for registration confirmation - ruby-on-rails

My rails application uses devise to handle registration, authentication, etc. I'm using the confirmable module. The bug is this– when a user registers with email, Devise is sending two confirmation emails with different confirmation links. One link works, the other directs the user to an error page.
Devise spits out a message associated with the error: "Confirmation token is invalid" and takes the user to the Resend Confirmation Email page.
I'm hosting with heroku and using sendgrid to send the emails. update: The bug also occurs on localhost.
I have no idea where the root of this bug is, and this might be more code than what you need to see:
models/user.rb
...
devise :database_authenticatable, :registerable, :omniauthable,
:recoverable, :rememberable, :trackable, :validatable,
:confirmable, :authentication_keys => [:login]
...
## callbacks
after_create :account_created
# called after the account is first created
def account_created
# check if this activiy has already been created
if !self.activities.where(:kind => "created_account").blank?
puts "WARNING: user ##{self.id} already has a created account activity!"
return
end
# update points
self.points += 50
self.save
# create activity
act = self.activities.new
act.kind = "created_account"
act.created_at = self.created_at
act.save
end
...
def confirmation_required?
super && (self.standard_account? || self.email_changed)
end
...
controllers/registrations_controller.rb
class RegistrationsController < Devise::RegistrationsController
def update
unless #user.last_sign_in_at.nil?
puts "--------------double checking whether password confirmation is required--"
## if the user has not signed in yet, we don't want to do this.
#user = User.find(current_user.id)
# uncomment if you want to require password for email change
email_changed = #user.email != params[:user][:email]
password_changed = !params[:user][:password].empty?
# uncomment if you want to require password for email change
# successfully_updated = if email_changed or password_changed
successfully_updated = if password_changed
params[:user].delete(:current_password) if params[:user][:current_password].blank?
#user.update_with_password(params[:user])
else
params[:user].delete(:current_password)
#user.update_without_password(params[:user])
end
if successfully_updated
# Sign in the user bypassing validation in case his password changed
sign_in #user, :bypass => true
if email_changed
flash[:blue] = "Your account has been updated! Check your email to confirm your new address. Until then, your email will remain unchanged."
else
flash[:blue] = "Account info has been updated!"
end
redirect_to edit_user_registration_path
else
render "edit"
end
end
end
end
controllers/omniauth_callbacks_controller
class OmniauthCallbacksController < Devise::OmniauthCallbacksController
skip_before_filter :verify_authenticity_token
def facebook
user = User.from_omniauth(request.env["omniauth.auth"])
if user.persisted?
flash.notice = "Signed in!"
# if the oauth_token is expired or nil, update it...
if (DateTime.now > (user.oauth_expires_at || 99.years.ago) )
user.update_oauth_token(request.env["omniauth.auth"])
end
sign_in_and_redirect user
else
session["devise.user_attributes"] = user.attributes
redirect_to new_user_registration_url
end
end
end
config/routes.rb
...
devise_for :users, controllers: {omniauth_callbacks: "omniauth_callbacks",
:registrations => "registrations"}
...
I'm happy to provide more information if needed. I'm also open to customizing/overriding the devise mailer behavior, but I don't know how to go about that.
Much thanks!

Solved!
I was able to override Devise::Mailer and force a stack trace to find out exactly what was causing duplicate emails. Devise::Mailer#confirmation_instructions was being called twice, and I found out that the problem was with my :after_create callback, shown below:
in models/user.rb...
after_create :account_created
# called after the account is first created
def account_created
...
# update points
self.points += 50
self.save
...
end
Calling self.save somehow caused the mailer to be triggered again. I solved the problem by changing when the points are added. I got rid of the after_create call and overrode the confirm! method in devise to look like this:
def confirm!
super
account_created
end
So now the user record doesn't get modified (adding points) until after confirmation. No more duplicate emails!

I originally went with Thomas Klemm's answer but I went back to look at this when I had some spare time to try and figure out what was happening as it didn't feel right.
I tracked the 'problem' down and noticed that it only happens when :confirmable is set in your devise (User) model and reconfirmable is enabled in the devise initializer - which in hindsight makes a lot of sense because essentially in the after_create we ARE changing the User model, although we aren't changing the email address - I suspect Devise may do this because the account isn't confirmed yet, but in any case it is easy to stop the second email just by calling self.skip_reconfirmation! in the after_create method.
I created a sample rails project with a couple of tests just to ensure the correct behaviour. Below are the key excerpts. If you have far too much time on your hands, you can see the project here: https://github.com/richhollis/devise-reconfirmable-test
app/models/User.rb
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :token_authenticatable, :confirmable,
# :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable, :confirmable
# Setup accessible (or protected) attributes for your model
attr_accessible :email, :password, :password_confirmation, :remember_me
after_create :add_attribute
private
def add_attribute
self.skip_reconfirmation!
self.update_attributes({ :status => 200 }, :without_protection => true)
end
end
initializers/devise.rb
# Use this hook to configure devise mailer, warden hooks and so forth.
# Many of these configuration options can be set straight in your model.
Devise.setup do |config|
..
..
# If true, requires any email changes to be confirmed (exactly the same way as
# initial account confirmation) to be applied. Requires additional unconfirmed_email
# db field (see migrations). Until confirmed new email is stored in
# unconfirmed email column, and copied to email column on successful confirmation.
config.reconfirmable = true
..
..
end
spec/models/user_spec.rb
require 'spec_helper'
describe User do
subject(:user) { User.create(:email => 'nobody#nobody.com', :password => 'abcdefghijk') }
it "should only send one email during creation" do
expect {
user
}.to change(ActionMailer::Base.deliveries, :count).by(1)
end
it "should set attribute in after_create as expected" do
user.status.should eq(200)
end
end
Running the rspec tests to ensure only one email is sent confirms the behaviour:
..
Finished in 0.87571 seconds 2 examples, 0 failures

Thanks for your great solution, Stephen! I've tried it and it works perfectly to hook into the confirm! method. However, in this case, the function is being called (as the name says) when the user clicks the confirmation link in the email he receives.
An alternative is to hook into the generate_confirmation_token method, so your method is called directly when the confirmation token is created and the email is sent.
# app/models/user.rb
def generate_confirmation_token
make_owner_an_account_member
super # includes a call to save(validate: false),
# so be sure to call whatever you like beforehand
end
def make_owner_an_account_member
self.account = owned_account if owned_account?
end
Relevant source of the confirmation module.

Related

Error Ruby on Rails: Users::OmniauthCallbacksController#facebook is missing a template for this request format and variant

I'm trying to add an external login with facebook, but whenever I realize the home page correctly directs me to the facebook page, but then I get the following error, and could not understand what it can be.
"Users::OmniauthCallbacksController#facebook is missing a template for this request format and variant. request.formats: ["text/html"] request.variant: [] NOTE! For XHR/Ajax or API requests, this action would normally respond with 204 No Content: an empty white screen. Since you're loading it in a web browser, we assume that you expected to actually render a template, not nothing, so we're showing an error to be extra-clear. If you expect 204 No Content, carry on. That's what you'll get from an XHR or API request. Give it a shot."
"That's what you'll get from an XHR or API request. Give it a shot."
raise ActionController::UnknownFormat, message
else
logger.info "No template found for #{self.class.name}\##{action_name}, rendering head :no_content" if logger
super
this is my controller
class Users::OmniauthCallbacksController < ApplicationController
def facebook
#User = User.from_omniauth(request.env["omniauth.auth"])
if #User.persisted?
#User.remember_me = true
sign_in_and_redirect #User, event: :authentication
end
end
end
this is the user model.
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable,:omniauthable, :omniauth_providers => [:facebook]
def self.from_omniauth(auth)
where(provider: auth[:provider], uid: auth[:uid]).first_or_create do |user|
if auth[:info]
user.email = auth[:info][:email]
user.name = auth[:info][:name]
end
user.password = Devise.friendly_token[0,20]
end
end
has_many :articles , :dependent => :destroy
end
and i put this line in config/initializers/divise.rb
config.omniauth :facebook, '504432376574467', 'b2bb80641fcc2ca4d28e48c5ce*******'
My guess would be that User.from_omniauth fails to create the user (possibly due to user.password and user.password_confirmation not matching), which causes the Users::OmniauthCallbacksController#facebook to reach the end of the method without going inside the if clause.
To check, you could for example add an else clause to your Facebook callback and raise an error in there.

Require password only when changing password devise registration

I have a registration/edit form that is rendered through a Devise::RegistrationsController within my application. The way it works now you must provide your current password when making any updates to the form. They way I want it to work is that the current_passwordis only required when you are updating the password field... or you must repeat the "new_password" as a means of confirmation. I have done some reading on the Devise Wiki, mainly the following links and they don't seem to list a solution for this. If anyone has some insight on how this might be achieved I would appreciate it.
https://github.com/plataformatec/devise/wiki/How-To:-Allow-users-to-edit-their-password
https://github.com/plataformatec/devise/wiki/How-To:-Allow-users-to-edit-their-account-without-providing-a-password
So I've been digging on trying to solve this I have found the solution.
In my model (user.rb) I added :validatable to the following snippet of code:
devise :omniauthable, :database_authenticatable, :registerable, :recoverable, :rememberable, :validatable,
:confirmable, :trackable, reset_password_keys:[:email, :company_id],
request_keys: [:host, :params], omniauth_providers: [:auth0]
Then in my registration controller, I added the following:
def update_resource(resource, params)
if !params[:password].blank?
resource.password = params[:password]
resource.password_confirmation = params[:password_confirmation]
end
resource.update_without_password(params)
end
And finally in my application controller, I added :password_confirmation to the following snippet:
devise_parameter_sanitizer.permit(:account_update) do |u|
u.permit(:first_name, :last_name, :email, :password, :password_confirmation, :phone_number, :receive_emails,
location_attributes: [:id, :street, :city, :state, :zip, :country])
end
With this combination, once the form gets submitted it will fall into the update_resource block that I have overwritten. It will check to make sure that resource.password and password_confirmation are the same. On top of that now that current_password is out of the equation, you no longer have to enter your password everytime to save changes.
What you can do is to validate at the time that the user do the update to its account, through the params params[:user][:password] and params[:user][:password_confirmation]:
This way if there are no password nor password_confirmation then use the update_without_password method passing the user_params, the other way do it just using the update_attributes method
def update
...
if params[:user][:password].blank? && params[:user][:password_confirmation].blank?
if #user.update_without_password(user_params)
flash[:notice] = 'User updated successfully'
redirect_to some_path
else
render :edit
end
else
if #user.update_attributes(user_params)
# login before update passing the current user
sign_in(User.find(current_user.id), :bypass => true)
flash[:notice] = 'User updated successfully'
redirect_to some_path
else
render :edit
end
end
...
end
Create a registration controller in app/controllers/registrations_controller.rb
class RegistrationsController < Devise::RegistrationsController
protected
def update_resource(resource, params)
# Require current password if user is trying to change password.
return super if params["password"]&.present?
# Allows user to update registration information without password.
resource.update_without_password(params.except("current_password"))
end
end

Ruby on Rails Devise Confirmation Token is nil

I've got a User model (created by $ rails g devise User) and it is set to use confirmable (in the model and migration).
When a User is created the confirmation token is not being set (and the confirmation email is not being sent).
Here's app/models/user.rb:
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable,
:confirmable, :lockable, :timeoutable
def password_required?
super if confirmed?
end
def password_match?
self.errors[:password] << "can't be blank" if password.blank?
self.errors[:password_confirmation] << "can't be blank" if password_confirmation.blank?
self.errors[:password_confirmation] << "does not match password" if password != password_confirmation
password == password_confirmation && !password.blank?
end
# new function to set the password without knowing the current
# password used in our confirmation controller.
def attempt_set_password(params)
p = {}
p[:password] = params[:password]
p[:password_confirmation] = params[:password_confirmation]
update_attributes(p)
end
# new function to return whether a password has been set
def has_no_password?
self.encrypted_password.blank?
end
# Devise::Models:unless_confirmed` method doesn't exist in Devise 2.0.0 anymore.
# Instead you should use `pending_any_confirmation`.
def only_if_unconfirmed
pending_any_confirmation {yield}
end
protected
def confirmation_required?
false
end
end
Any ideas?
That's because you are overriding confirmation_required? to always return false.
Take a look at this
before_create :generate_confirmation_token, if: :confirmation_required?
The token is only generated if that method returns true.
The default behavior of confirmation_required? is to return true if the record hasn't been confirmed.
def confirmation_required?
!confirmed?
end
To complement #nbermudezs answer, this confirmation_required? method was added to devise in case you want to bypass confirmation for some users (eg users with special promo code, or whatever)
If you don't want to have any exceptions, I suggest you simply remove those lines of code or comment them, so you return to the default behavior of devise_confirmable which is the one you seem to want (and the one given by #nbermudezs)
# def confirmation_required?
# false
# end

Configuring rails grape api with devise token

I am trying to configure token generation with devise in a grape api rails app. Since I have the current version of devise, token generation has been disabled. I am having several issues. First, is that when I submit a username and password to the sessions controller, it gives me an error that "ensure_authentication_token":
undefined method `ensure_authentication_token!' for #<User:0x007f880cca9090>
This is so strange because as you can see below, I have it defined in my user model and when I manually create Users in rails console, it works properly.
Is that a scope issue or why is that occurring?
User Model:
class User < ActiveRecord::Base
before_save :ensure_authentication_token
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
def ensure_authentication_token
if authentication_token.blank?
self.authentication_token = generate_authentication_token
end
end
private
def generate_authentication_token
loop do
token = Devise.friendly_token
break token unless User.where(authentication_token: token).first
end
end
end
Sessions Grape API Controller:
module API
module V1
class Sessions < Grape::API
include API::V1::Defaults
resource :sessions do
params do
requires :email, type: String, desc: "Email"
requires :password, type: String, desc: "Password"
end
post do
email = params[:email]
password = params[:password]
if email.nil? or password.nil?
error!({error_code: 404, error_message: "Invalid Email or Password."},401)
return
end
user = User.where(email: email.downcase).first
if user.nil?
error!({error_code: 404, error_message: "Invalid Email or Password."},401)
return
end
if !user.valid_password?(password)
error!({error_code: 404, error_message: "Invalid Email or Password."},401)
return
else
user.ensure_authentication_token!
user.save
{status: 'ok', token: user.authentication_token}.to_json
end
end
end
end
end
end
The second problem is that when I follow this blog, it says that I need to add the following authentication check in my defaults.rb in the base api controller. When I add the "before do" section, I get access denied error even if I enter in the right credentials and it doesn't even go on to the rest of the sessions controller that I mentioned above.
before do
error!("401 Unauthorized, 401") unless authenticated
end
helpers do
def warden
env['warden']
end
def authenticated
return true if warden.authenticated?
params[:access_token] && #user = User.find_by_authentication_token(params[:access_token])
end
def current_user
warden.user || #user
end
end
Thanks for any help you can give!
EDIT: Phillip was absolutely correct that one of these issues was due to the bang versus non banged version of ensure_authentication_token. Removing the ! from the controller fixed that issue. The other problem was indeed from adding the "before do" loop I had.
This is so close to working, I can give and receive tokens in my api but when it connects to ember, it complains about a lack of a csrf token even though I have "protect_from_forgery with: :null_session" set in my application.rb
In your User model, you define a method called ensure_authentication_token.
In your Session controller, you call a method called ensure_authentication_token!.
These are not the same method: Why are exclamation marks used in Ruby methods?
This is preventing you from generating an authentication token, which probably explains the "401 Unauthorized, 401" error.

Disable Devise confirmable mails

Recently I added the confirmable module to my User class. I already have a quite nice mailing system (Sidekiq, Sendgrid...) in my app, so I created my own "confirm account" mail. The problem now is to disable Devise from sending its default email. Is there any way to completely disable the Devise mailing system?
Added:
I want to maintain the confirmable module, as I am using its attributes and routes.
I can't use skip_confirmation! because I want the users to confirm their account.
I just want to disable Devise mails.
Use the source, Luke:
# lib/devise/models/confirmable.rb
# A callback method used to deliver confirmation
# instructions on creation. This can be overriden
# in models to map to a nice sign up e-mail.
def send_on_create_confirmation_instructions
send_devise_notification(:confirmation_instructions)
end
So override this method in your model to do nothing.
Try overriding the following devise method in your model:
def confirmation_required?
!confirmed?
end
or use skip_confirmation!:
user = User.new(params)
user.skip_confirmation!
user.save!
Use skip_confirmation! method before saving any object.
def create
#user = User.new(params[:user])
#user.skip_confirmation!
#user.save!
end
I think just removing
:confirmable
from the user model should do it
or have you tried disabling
config/environments/development.rb
config.action_mailer.default_url_options = { :host => 'localhost:3000' }
I recommend you
User.skip_reconfirmation!
That is skip confirm mail and update email not to use "confirm!"
remove (:confirmable) from devise model
ex:- here my devise model is User
here I used like this.
class User < ActiveRecord::Base
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable,:omniauthable
end

Resources