I'm using Omniauth to allow users to sign in (and create an account) through Facebook, Twitter, and Google.
However, if a user decides to not use those services anymore, but continue to use their account, they will want to add a password.
How can I let a user add a password to their account without entering a current password?*
When I let a user go to the edit_user_registration_path(#user), they see the form below:
= form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put, name: 'validation'}) do |f|
= devise_error_messages!
= f.label :email, class: 'block'
= f.email_field :email, class: 'large'
%span.infobar Keep this here unless you'd like to change your email
= f.label :password, class: 'block'
= f.password_field :password, class: 'large'
%span.infobar Leave blank if you don't want to change it
= f.label :password_confirmation, class: 'block'
= f.password_field :password_confirmation, class: 'large'
- if #user.password_required?
= f.label :current_password, class: 'block'
= f.password_field :current_password, class: 'large'
%span.infobar We need your current password to confirm changes
= f.submit "Update"
user.rb
def password_required?
(authentications.empty? || !password.blank?) && super
end
As you can see, I have it so that if #user.password_required?, then show the current password field. Even though this field is not displayed when attempting to create a new password for the account, the form will not validate correctly, as a current password is required.
I just overwrote the RegistrationsController#update method:
class RegistrationsController < Devise::RegistrationsController
def update
# Override Devise to use update_attributes instead of update_with_password.
# This is the only change we make.
if resource.update_attributes(params[resource_name])
set_flash_message :notice, :updated
# Line below required if using Devise >= 1.2.0
sign_in resource_name, resource, :bypass => true
redirect_to after_update_path_for(resource)
else
clean_up_passwords(resource)
render_with_scope :edit
end
end
end
Source: How To: Allow users to edit their account without providing a password
Related
I have some custom fields in for my Devise registrations controller, and set this up in Application_controller:
def configure_permitted_parameters
devise_parameter_sanitizer.for(:account_update) { |u| u.permit(:first_name, :last_name, :email, :password, :password_confirmation, :current_password, :remember_me) }
end
Here is my edit registration form:
<%= simple_form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put }) do |f| %>
<%= f.error_notification %>
<div class="form-inputs">
<%= f.input :email, required: true, autofocus: true, placeholder: "Email" %>
<br>
<%= f.input :password, autocomplete: "off", hint: "Leave blank if you are not changing your password", required: false, placeholder: "Password" %>
<br>
<%= f.input :password_confirmation, required: false, placeholder: "Password Confirmation" %>
<div class="form-actions">
<%= link_to "Back to Home Page", user_landings_path, :class => 'btn btn-success' %>
<%= f.submit 'Update', :class => 'btn btn-primary' %>
</div>
<% end %>
The email change saved, but not the password.
Any tips?
Edit:
I also am bypassing the requirement that users enter in their current password to update their account, if that matters:
class RegistrationsController < Devise::RegistrationsController
protected
def update_resource(resource, params)
resource.update_without_password(params)
end
end
I have solved this in the past by manually assigning the password to the resource. Overwrite the devise method update_resource in your RegistrationsController (or wherever you may need it). This will insure that the password is updated if it is passed in the params, and then handle any other attributes.
def update_resource(resource, params)
if params[:password]
resource.password = params[:password]
resource.password_confirmation = params[:password_confirmation]
end
resource.update_without_password(params)
end
It's a little bit of a monkey patch but gets the job done.
This behaviour is exactly what Divises authors want it to be. They do not want user to change the password without presenting current one because it's possible security issue. Say you leave your machine unlocked while went for coffee and your roommate changed password on the website you been logged in. Not cool, right? So as I decided for myself the best thing to do is to separate edit profile and change password actions, so you can change profile without presenting current_password, but for password change you should present it.
The exact reason why password not updating in Devises update_without_password
method implementation (note params.delete and authors' comment):
# Updates record attributes without asking for the current password.
# Never allows a change to the current password. If you are using this
# method, you should probably override this method to protect other
# attributes you would not like to be updated without a password.
def update_without_password(params, *options)
params.delete(:password)
params.delete(:password_confirmation)
result = update_attributes(params, *options)
clean_up_passwords
result
end
Now note how they make password optional in update_with_password method (note id params[:password].blank? block):
def update_with_password(params, *options)
current_password = params.delete(:current_password)
if params[:password].blank?
params.delete(:password)
params.delete(:password_confirmation) if params[:password_confirmation].blank?
end
result = if valid_password?(current_password)
update_attributes(params, *options)
else
self.assign_attributes(params, *options)
self.valid?
self.errors.add(:current_password, current_password.blank? ? :blank : :invalid)
false
end
clean_up_passwords
result
end
So exact solution to your question is to override update_without_params method to remove password from params conditionally as they do in update_with_password method. But personally I don't advise to do this because of possible security issue. Better solution in my opinion is to separate views of profile edit and password change.
Assume my current passwort is 1234 and it has to be at least 4 characters long.
I have 3 input fields in a form:
new password
new password confirmation
current password
when I use update_with_password and the input
new password: 5678
new password confirmation: 5678
current paswword: 1234
It updates successfully
If for instance I use
new password: 12
new password confirmation: 23
current password: 1234
I get multiple devise errors: password too short, passwords don't match etc.
when I use update_without_password I expect I only need to remove the current password field and everything would stay the same.
Instead if I do that and give the input:
new password: 12
new password confirmation: 34
I get the message account updated successfully and the user record is not updated
this is my controller:
class Users::RegistrationsController < Devise::RegistrationsController
def edit
#images = Dir.glob("public/assets/images/users/#{current_user.id}/med/*")
end
def update
if params[:image_file_path]
ff = File.open("public/"+params[:image_file_path])
resource.image = ff
resource.save!
end
self.resource = resource_class.to_adapter.get!(send(:"current_#{resource_name}").to_key)
prev_unconfirmed_email = resource.unconfirmed_email if resource.respond_to?(:unconfirmed_email)
resource_updated = resource.update_without_password(account_update_params)
yield resource if block_given?
if resource_updated
if is_flashing_format?
flash_key = update_needs_confirmation?(resource, prev_unconfirmed_email) ?
:update_needs_confirmation : :updated
set_flash_message :notice, flash_key
end
sign_in resource_name, resource, bypass: true
respond_with resource, location: after_update_path_for(resource)
else
#images = Dir.glob("public/assets/images/users/#{current_user.id}/med/*")
clean_up_passwords resource
respond_with resource
end
end
private
def sign_up_params
params.require(:user).permit(:first_name, :last_name, :email, :password, :password_confirmation, :telephone, :image, :address, :birthday)
end
def account_update_params
params.require(:user).permit(:first_name, :last_name, :email, :password, :password_confirmation, :telephone, :image, :image_file_path, :address, :birthday)
end
protected
def update_resource(resource, params)
resource.update_without_password(params)
end
def after_update_path_for(resource)
edit_user_registration_path
end
end
this is my view:
<%= form_for(resource, as: resource_name, url: registration_path(resource_name), html: {method: :put }) do |f| %>
<div class="form-group">
<%= f.label :password, "Change Password", class: 'control-label' %>
<i>(leave blank if you don't want to change it)</i><br />
<%= f.password_field :password, autocomplete: "off", class: 'form-control' %>
</div>
<div class="form-group">
<%= f.label :password_confirmation, "New Password Confirmation", class: 'control-label' %><br />
<%= f.password_field :password_confirmation, autocomplete: "off", class: 'form-control' %>
</div>
<%= f.submit "Save", class: 'btn btn-success' %>
<%end%>
From http://www.rubydoc.info/github/plataformatec/devise/Devise/Models/DatabaseAuthenticatable:update_without_password:
Updates record attributes without asking for the current password. Never allows a change to the current password. If you are using this method, you should probably override this method to protect other attributes you would not like to be updated without a password.
It likely silently ignores the password and password_confirmation parameters in your request.
I am building a simple app that allows members to create a trip. Only one trip. I wanted to get some practice using the devise gem so that setting up the application with sign in, sign out, etc is easy and efficient, but I need some help. Right now I have two models: member & trip, neither of which have a controller.
This is my sign up form and when I click submit, somehow a member is created in the database even without a member controller? How do I redirect this to a different page after submit is pressed?
I'm just a little confused on what I should be adding since devise is doing a lot of the work behind the scenes.
<h2>Sign up</h2>
<%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %>
<%= devise_error_messages! %>
<div><%= f.label :email %><br />
<%= f.email_field :email, autofocus: true %></div>
<div><%= f.label :password %><br />
<%= f.password_field :password, autocomplete: "off" %></div>
<div><%= f.label :password_confirmation %><br />
<%= f.password_field :password_confirmation, autocomplete: "off" %></div>
<div><%= f.submit "Sign up" %></div>
<% end %>
You create a controller to do this.
class UserController < Application::Base
def new
#user = User.new
end
def create
#user = User.new(email: params[:email], password: params[:password], password_confirmation: params[:password_confirmation])
if #user.save
redirect_to <page you want to redirect to>
else
render new
end
end
The user(or in you case member) controller simply talking resides in devise gem internals.
To redirect a user to user/home you may define user_root route in your routes.rb file in this way:
get 'user/home', as: 'user_root'
Read more here.
Devise offers some macros that you can override to choose what paths you want to redirect to.
class RegistrationsController < Devise::RegistrationsController
protected
def after_sign_up_path_for(resource)
'/an/example/path'
end
end
Source
EDIT:
Create a file in your controller folder called registrations_controller.rb. Put this code into that file.
class RegistrationsController < Devise::RegistrationsController
protected
def after_sign_up_path_for(resource)
'/an/example/path'
end
end
Replace /an/example/path with whatever path you want to redirect to after someone signs up. Then modify your config/routes.rb to point to that controller by adding this line:
devise_for :members, :controllers => { :registrations => "registrations" }
Then, you may need to edit your config/application.rb by adding this line
config.paths['app/views'] << "app/views/devise"
If you encounter a "MissingTemplate" error.
Working on a password reset mechanism for users. The password length validation is triggering and I'm trying to understand why.
user.rb
class User < ActiveRecord::Base
has_secure_password
validates :password, length: { minimum: 6 }
...
def create_password_reset_token
self.update_attributes!(password_reset_token: SecureRandom.urlsafe_base64, password_reset_sent_at: Time.zone.now)
end
def reset_password(params)
self.update_attributes!(params)
self.update_attributes!(password_reset_token: nil, password_reset_sent_at: nil)
end
end
password_resets_controller.rb
def create
user = User.find_by_email(params[:email])
if user
user.create_password_reset_token
UserMailer.password_reset_email(user).deliver
redirect_to root_url, :notice => "Email sent with password reset instructions!"
else
flash[:error] = "A user with that email address could not be found."
render 'new'
end
end
def edit
#user = User.find_by_password_reset_token(params[:id])
if #user
render 'edit'
else
flash[:error] = "Invalid password reset code."
redirect_to root_url
end
end
def update
#user = User.find_by_password_reset_token(params[:id])
if #user.password_reset_sent_at < 2.hours.ago
flash[:error] = "Password reset has expired."
redirect_to new_password_reset_path
elsif #user.reset_password(user_params)
flash[:success] = "Password has been reset."
redirect_to root_url
else
render 'edit'
end
end
password_resets/new.html.erb:
<%= form_tag password_resets_path, :method => :post do %>
<%= label_tag :email %>
<%= text_field_tag :email, params[:email] %>
<%= submit_tag "Reset Password" %>
<% end %>
password_resets/edit.html.erb:
<%= form_for #user, :url => password_reset_path(params[:id]) do |f| %>
<h1 class="centertext">Reset Password</h1>
<%= render 'shared/error_messages', object: f.object %>
<%= f.label :password %>
<%= f.password_field :password %>
<%= f.label :password_confirmation, "Confirm password" %>
<%= f.password_field :password_confirmation %>
<%= f.submit "Update password" %>
<% end %>
The error is:
Validation failed: Password is too short (minimum is 6 characters)
The line that throws it is inside the create_password_reset_token method:
self.update_attributes!(password_reset_token: SecureRandom.urlsafe_base64, password_reset_sent_at: Time.zone.now)
Why does the validation trigger here? I'm not doing anything with the password itself. I'm simply creating a token and a time inside the user record.
Changing the validation to say on: :create makes it not trigger. The problem is that then users are able to reset their password to something fewer than six characters.
CLARIFICATION
To be clear, the order of operations is:
User clicks a link saying "I forgot my password."
They are taken to password_reset_controller/new.html.erb. This form has one field: email address. They enter their email and submit it.
Controller checks to see if that user exists. If it does, it tells the model to generate a password_reset_token.
Controller then orders an email to be sent to the user with a URL that contains the token.
The user clicks the URL. If the token is valid, they are taken to edit.html.erb and they enter their new email and its confirmation.
The controller calls the reset_password method, which actually resets the user's password.
Currently, the validation triggers on step 2, after they enter their email and click submit.
your create_password_reset_token is calling update_attributes which will trigger validations on every field in your User model and hence trigger the password validation as it doesn't have a current one set
you would need to either
1) Use update_attribute for those specific fields and that wouldn't trigger the validation
2) Add some password_reset field or enum to your model and set that to true when the password reset button is clicked and then do something like this in your user model
has_secure_password :validations => false
validates :password, length: {minimum: 6}, unless: -> { user_password_reset? }
3) Use the devise gem to take care of this for you
Update:
Try this
def create_password_reset_token
self.update_attribute(:password_reset_token, SecureRandom.urlsafe_base64)
self.update_attribute(:password_reset_sent_at, Time.zone.now)
end
I resolved this by adding a Proc statement to the password validation, like so:
validates :password, length: { minimum: 6 }, unless: Proc.new { |a| !a.password_reset_token.nil? }
Now the validation runs both during user creation and password reset, but not during the interval when there is a password reset token set. All tests are passing.
I am using ActiveAdmin as my administration backend in my rails app. Basically, I have an admin_user and a user model.
When I create a new user from an admin account, I specify an email and a password, that is ok.
Let's say I then want to modify the user's email but not the password... it seems this cannot be done as the password field cannot be blank when updating a user.
Is there a configuration somewhere that would consider that the password is unchanged is the fields (password and password_confirmation) are left blank while updating a user?
You don't really need to mess at all with Devise's registration controller, you can just ignore empty password fields inside ActiveAdmin's resource controller:
ActiveAdmin.register User do
controller do
def update
model = :user
if params[model][:password].blank?
%w(password password_confirmation).each { |p| params[model].delete(p) }
end
super
end
end
end
Devise provides an update_without_password method that you can use when updating a user if no password is entered. Using that method, you can customize the update method in your ActiveAdmin users controller.
def update
#user = User.find(params[:id])
if params[:user][:password].blank?
#user.update_without_password(params[:user])
else
#user.update_attributes(params[:user])
end
if #user.errors.blank?
redirect_to admin_users_path, :notice => "User updated successfully."
else
render :edit
end
end
The Devise Wiki has more information about this method if your interested.
You need to evaluate password and password_confirmation in the if statement, to apply the validations on "password_confirmation", eg for my case:
#app/admin/user.rb
controller do
def update
if params[:user][:password].blank? && params[:user][:password_confirmation].blank?
params[:user].delete("password")
params[:user].delete("password_confirmation")
end
super
end
end
#app/model/user.rb
validates :name, :email, presence: true
validates :password, :password_confirmation, presence: true, on: :create
validates :password, confirmation: true
This allows me to validate password presence only when I create a new user and update without changing his password.
This work for me, I hope this is helpful.
I hope this is helpful.
You can validate the password only on create because bcrypt when updating will still validate password presence.
class User
validate :password, :password_confirmation, presence: true, on: :create
end
In my opinion this is much simpler while causing no risk, and allows you to use a single partial form for create and update routes with an if statement showing/not showing password input field like so:
<%= form_for(user, url: users_path) do |form| %>
<%= form.label 'Name' %><br>
<%= form.text_field :name%><br>
<%= form.label 'email' %><br>
<%= form.text_field :email%><br>
<%= form.label 'Password' %><br>
<%= form.password_field :password%><br>
**<% if form.object.new_record? %>**
<%= form.label 'password_confirmation' %><br>
<%= form.password_field :password_confirmation%><br>
**<% end %>**
<%= form.submit (form.object.new_record? ? 'create' : 'update') %>
As told in comments on the #mauriceomdea answer, the def update is missing (or at least was missing for me, henerating an error.)
Here is a more complete version that worked for me :
You don't really need to mess at all with Devise's registration controller, you can just ignore empty password fields inside ActiveAdmin's resource controller:
ActiveAdmin.register User do
controller do
def update
model = :user
if params[model][:password].blank?
%w(password password_confirmation).each { |p| params[model].delete(p) }
end
super
end
end
end
hope this helps someone.