When a user changes their email address, the new email is not confirmed. However devise doesn't update/reset the confirmed_at field.
I have tried:
before_update :updateEmailConfirmed, if: :email_changed?
def updateEmailConfirmed
# check if there is an unconfirmed_email
user = User.find(id)
if !user.unconfirmed_email.nil?
# set confirmed_at to nil
self.update!(confirmed_at:nil)
end
end
I understand the :confirmed_at
field is for any confirmation, so it is working as expected. However I am using this field to track to see if the email has been verified.
Currently I have added an extra field to my User model called :email_confirmed
of type bool and I set that to true/false depending on whether the current :email
field has been verfiied.
My question is, is there anything built into the Devise modules that will allow me to do this without introducing any new columns to my User table and modifying my User class.
Update1.)
Here are the tags set for my User model:
class User < ApplicationRecord
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable,
:confirmable
protected
def confirmation_required?
false
end
end
This is what my User table looks like:
create_table "users", force: :cascade do |t|
t.string "email", default: "", null: false
t.string "encrypted_password", default: "", null: false
t.string "reset_password_token"
t.datetime "reset_password_sent_at"
t.datetime "remember_created_at"
t.integer "sign_in_count", default: 0, null: false
t.datetime "current_sign_in_at"
t.datetime "last_sign_in_at"
t.string "current_sign_in_ip"
t.string "last_sign_in_ip"
t.string "confirmation_token"
t.datetime "confirmed_at"
t.datetime "confirmation_sent_at"
t.string "unconfirmed_email"
t.boolean "verified", default: false
t.index ["confirmation_token"], name: "index_users_on_confirmation_token",
unique: true
t.index ["email"], name: "index_users_on_email", unique: true
t.index ["reset_password_token"], name:
"index_users_on_reset_password_token", unique: true
end
Best I could think of is Overriding the Devise::Mailer
and the Confirmation::Controller
provided by Devise.
I realised I can pass in a type parameter inside the confirmation_url that gets sent to the users email.
class DeviseMailer < Devise::Mailer
def confirmation_instructions(record, token, opts={})
# determine if this request is to change the email
# or verify the existing email
if !record.unconfirmed_email.nil?
type = "change_email"
else
type = "verify_email"
end
# get the link for the confirmation link and append
# the type paramater to the end
confirmation_link = Rails.application
.routes.url_helpers
.user_confirmation_url(confirmation_token: token).to_s + "&type="+type.to_s
.
.
.
# send email with new confirmation link
end
end
Then on the other end, when someone clicks on the confirmation links, I can distinguish the type of request by looking at the parameters:
class ConfirmationsController < Devise::ConfirmationsController
def show
# confirm user
self.resource = resource_class.confirm_by_token(params[:confirmation_token])
if resource.errors.empty?
# sign in user if they are not already signed in
sign_in(resource_name, resource)
# find out what kind of request this confirmation was for
type = params[:type]
if type == "verify_email"
flash[:confirmed] = "Email successfully confirmed!"
# update verified field which is used to keep track if the :email field has been verified
current_user.update(verified:true)
elsif type == "change_email"
flash[:confirmed] = "Email successfully updated!"
# update verified field which is used to keep track if the :email field has been verified
current_user.update(verified:false)
# send confirmation instructions for this new email so we can verify it
current_user.send_confirmation_instructions
end
respond_with_navigational(resource){ redirect_to root_path }
else
p resource.errors
flash[:unconfirmed] = "Something went wrong trying to confirm your email? Contact contact@email.com"
respond_with_navigational(resource.errors, status: :unprocessable_entity){ redirect_to edit_user_registration_path }
end
end
private
def after_confirmation_path_for(resource_name, resource)
sign_in(resource) # In case you want to sign in the user
root_path
end
end