I'm wondering if there is a manner of calling the 'forgot password' procedure without forcing my user to log out
The case I'm running into is:
since devise requires a password to change these fields, as it should, the user is unable to modify them
I had thought about just not forcing the password to be set but that doesn't make sense to security wise so instead I just display the fields as text and notify the user to follow the 'forgot password' procedure in order to set a password and then they can change the fields
The issue then is that I cannot simply link to this from the user profile since devise will tell the user that they can't do this while already logged in.
So is there a manner of overriding the forgot password or /users/password/edit method so that a logged-in user can perform this action as well?
My complete solution here, because I then also learned that the user would have to log out after clicking the link in the email, was to add an some additional UserController actions for actually editing the password as well as saving it. This is not an ideal solution and cold probably be done in a better manner but it works for me.
users controller; added methods to do the reset
before_filter :authenticate_user!, :except => [:do_reset_password, :reset_password_edit]
def reset_password
id = params[:id]
if id.nil?
id = current_user.id
end
if (!user_signed_in? || current_user.id.to_s != id.to_s)
flash[:alert] = "You don't have that right."
redirect_to '/home'
return
end
@user = User.find(id)
@user.send_reset_password_instructions
respond_to do |format|
format.html { redirect_to '/users/edit', notice: 'You will receive an email with instructions about how to reset your password in a few minutes.' }
end
end
def do_reset_password
id = params[:id]
if id.nil? && !current_user.nil?
id = current_user.id
end
if id.nil?
@user = User.where(:reset_password_token => params[:user][:reset_password_token]).first
else
@user = User.find(id)
end
if @user.nil? || @user.reset_password_token.to_s != params[:user][:reset_password_token]
flash[:alert] = "Url to reset was incorrect, please resend reset email."
redirect_to '/home'
return
end
# there may be a better way of doing this, devise should be able to give us these messages
if params[:user][:password] != params[:user][:password_confirmation]
flash[:alert] = "Passwords must match."
redirect_to :back
return
end
if @user.reset_password!(params[:user][:password],params[:user][:password_confirmation])
@user.hasSetPassword = true
@user.save
respond_to do |format|
format.html { redirect_to '/home', notice: 'Your password has been changed.' }
end
else
flash[:alert] = "Invalid password, must be at least 6 charactors."
redirect_to :back
end
end
def reset_password_edit
@user = User.where(:reset_password_token => params[:reset_password_token]).first
if @user.nil? || !@user.reset_password_period_valid?
flash[:alert] = "Password reset period expired, please resend reset email"
redirect_to "/home"
return
end
end
views/devise/registrations/edit; changed the view to not let the user edit fields that require a password
<%= form_for(resource, :as => resource_name, :url => registration_path(resource_name), :html => { :method => :put }) do |f| %>
<%= devise_error_messages! %>
<% if !resource.hasSetPassword %>
<%= f.label :name %><br />
<p style="line-height:24px;"><b><%= @user.name %></b></p>
<div><%= f.label :email %><br />
<p style="line-height:24px;"><b><%= @user.email %> </b></p>
<p style="position:relative; left:150px; width:420px;">
<i>you cannot change any settings because you have not set a password <br />yet, you can do so by following the </i>
<%= link_to "Forgot your password", "/users/reset_password" %> <i> procedure</i>
</p>
</div>
<% else %>
<p><%= f.label :name %><br />
<%= f.text_field :name %></p>
<div><%= f.label :email %><br />
<%= f.email_field :email %></div>
<div><%= f.label :password %> <br />
<%= f.password_field :password %><i>(leave blank if you don't want to change it)</i></div>
<div><%= f.label :password_confirmation %><br />
<%= f.password_field :password_confirmation %></div>
<div><%= f.label :current_password %> <br />
<%= f.password_field :current_password %>
<i>(we need your current password to confirm your changes)</i>
</div>
<div><%= f.submit "Update" %></div>
<% end %>
<% end %>
views/devise/mailer/reset_password_instructions; had to change it to point to the right URL in our new case
<p>Hello <%= @resource.email %>!</p>
<p>Someone has requested a link to change your password, and you can do this through the link below.</p>
<% if !@resource.hasSetPassword %>
<p><%= link_to 'Change my password', 'http://streetsbehind.me/users/reset_password_edit?reset_password_token='+@resource.reset_password_token %></p>
<!-- todo: there's probably a better way of doing this than just hardcoding streetsbehind.me -->
<% else %>
<p><%= link_to 'Change my password', edit_password_url(@resource, :reset_password_token => @resource.reset_password_token) %></p>
<% end %>
<p>If you didn't request this, please ignore this email.</p>
<p>Your password won't change until you access the link above and create a new one.</p>
views/users/reset_password_edit.erb
<%= form_for(@user, :url => url_for(:action => :do_reset_password) , :html => { :method => :post }) do |f| %>
<%= f.hidden_field :reset_password_token %>
<div><%= f.label :password, "New password" %><br />
<%= f.password_field :password %></div>
<div><%= f.label :password_confirmation, "Confirm new password" %><br />
<%= f.password_field :password_confirmation %></div>
<div><%= f.submit "Change my password" %></div>
<% end %>
config/routes.rb
get "users/reset_password"
get "users/reset_password_edit"
resource :users do
post 'do_reset_password'
end