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.
Related
I have a two model setup with devise, users and profiles, with devise 4.2. I have implemented nested attributes with custom views as shown here: Rails 4.0 with Devise. Nested attributes Unpermited parameters
On my registrations/edit.html.haml view, when a user submits the form they must provide their current password by default. However, if the nested attributes are edited, and the password is not provided, the form will still update the nested attributes. How do I prevent this from happening?
registrations_controller.rb:
class RegistrationsController < Devise::RegistrationsController
before_filter :configure_permitted_parameters
def new
build_resource({})
self.resource.profile = Profile.new
respond_with self.resource
end
protected
def configure_permitted_parameters
devise_parameter_sanitizer.permit(:account_update) do |u|
u.permit(<user fields>, profile_attributes: [<:profile_fields>])
end
devise_parameter_sanitizer.permit(:sign_up) do |u|
u.permit(<user_fields>, profile_attributes: [<profile_fields>])
end
end
end
registrations/edit.html.haml:
.authform
%h3
Edit #{resource_name.to_s.humanize}
= form_for(resource, :as => resource_name, :url => user_registration_path, :html => { :method => :patch, :role => 'form'}) do |f|
= devise_error_messages!
.form-group
= f.label :email
= f.email_field :email, class: 'form-control'
- if devise_mapping.confirmable? && resource.pending_reconfirmation?
%div
Currently waiting confirmation for: #{resource.unconfirmed_email}
%fieldset
%p Leave these fields blank if you don't want to change your password.
.form-group
= f.label :password
= f.password_field :password, :autocomplete => 'off', class: 'form-control'
.form-group
= f.label :password_confirmation
= f.password_field :password_confirmation, class: 'form-control'
%fieldset
= f.fields_for :profile do |profile_fields|
.form-group
= profile_fields.label :a_field_1
= profile_fields.number_field :a_field_1, min: 0, max: 8
.form-group
= profile_fields.label :a_field_2
= profile_fields.number_field :a_field_2, min: 0, max: 8
.form-group
= profile_fields.label :a_field_1
= profile_fields.text_field :a_field_2, class: 'form-control'
%fieldset
%p You must enter your current password to make changes.
.form-group
= f.label :current_password
= f.password_field :current_password, class: 'form-control'
= f.submit 'Update', :class => 'button right'
It turned out that the root cause was an internal call to rails assign_attributes method which assigns nested attributes even if the password is invalid. The fix was to over-ride the update_resource method in the registrations controller with the following code
def update_resource(resource, params)incorrect
unless resource.valid_password?(params[:current_password])
resource.errors.add(:current_password, params[:current_password] ? :blank : :invalid)
return resource
end
resource.update_with_password(params)
end
Which checks if the password is valid and stops the assignment process if it is not.
I've been going thru Rails Tutorial and got stucked on chapter 7. The problem is that after posting sign up information to rails thru rails console (with User.new), id, created_at and updated_at are becoming nil. By creating User thru creaded user web page, email becomes nil.
user.rb
class User < ApplicationRecord
before_save { self.email = email.downcase! }
VALID_EMAIL_REGEX = /\A[\w+\-.]+#[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i
validates :name, presence: true, length: { maximum: 50 }
validates :email, presence: true, length: { maximum: 255 },
format: { with: VALID_EMAIL_REGEX },
uniqueness: { case_sensitive: false }
validates :password, presence: true, length: { minimum: 6 }
has_secure_password
end
users_controller.rb
class UsersController < ApplicationController
def new
#user = User.new
end
def create
#user = User.new(user_params)
if #user.save
flash[:success] = "Welcome to the Sample App!"
redirect_to #user
else
render 'new'
end
end
def show
#user = User.find(params[:id])
end
private
def user_params
params.require(:user).permit(:name, :email, :password,
:password_confirmation)
end
end
* new.html.erb *
<% provide(:titel, 'Sign Up') %>
<h1>Sign Up</h1>
<div class="row">
<div class="col-md-6 col-md-offset-3">
<%= form_for(#user, url: signup_path) do |f| %>
<%= render 'shared/error_messages' %>
<%= f.label :name %>
<%= f.text_field :name, class: "form-control" %>
<%= f.label :email %>
<%= f.email_field :email, class: 'form-control' %>
<%= f.label :password %>
<%= f.password_field :password, class: "form-control" %>
<%= f.label :password_confirmation, "Confirmation" %>
<%= f.password_field :password_confirmation, class: "form-control" %>
<%= f.submit "Create my account", class: "btn btn-primary" %>
<% end %>
</div>
</div>
rails console output
irb(main):001:0> user = User.new(name: "Test User", email: "test#user.com", password: "secretsecret", password_confirmation: "secretsecret")
=> #<User id: nil, name: "Test User", email: "test#user.com", created_at: nil, updated_at: nil, password_digest: "$2a$10$E1SvEDh.aQSi809sQ7ecReacmSsnxRDUn3IbEawugQD...">
Showing /sample_app/app/views/users/show.html.erb where line #7 raised:
def gravatar_for(user, size: 100)
gravatar_id = Digest::MD5::hexdigest(user.email.downcase) # this line is highlighted
gravatar_url = "https://secure.gravatar.com/avatar/#{gravatar_id}?=#{size}"
image_tag(gravatar_url, alt: user.name, class: "gravatar")
end
by checking development.sqlite3, email field is nil.
Where am I wrong? Thank you!
Ruby 2.4, Rail 5.1
User.new does not actually make a post request to the controller. It just creates a new instance of the User object in memory without saving it to the database. If you use User.create with the same arguments you will get an id, an updated at, and a created_at - but this still is not making a post request to your controller, its simply saving it to your database. If you'd like to make a post request to the new action of your controller you can make a front end with a new user form and submit it, or you could use something like postman or curl to make a request to your server. You'd also have to have your serving running either locally or elsewhere. I recommend reviewing pure ruby before going deeper into rails.
https://www.ruby-lang.org/en/documentation/quickstart/3/
https://www.getpostman.com/
https://curl.haxx.se/docs/manpage.html
After long time seeking for errror, it's finaly cought! The main casus on it were before_save { self.email = email.downcase! } pice of code. in user.rb model.before_save { self.email = email.downcase }, so, without forcing downcase!
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.
I'm relatively new to Rails (using Rails 4), and am having a problem with validation for my user model. Even when the form is fully filled in with both the passwords, when I submit the code two errors print out:
{:password=>["can't be blank"], :password_confirmation=>["doesn't match Password"]}
I would like the user to be saved into the database, but these validation errors are preventing that from happening. What I would like to know is what I need to change in order to get rid of these errors.
I am printing out the params object and it looks like this (the authenticity token is omitted here):
params: {"utf8"=>"✓","authenticity_token"=>"[omitted]",
"user"=>{"username"=>"testuser1", "password"=>"test",
"password_confirmation"=>"test", "email_attributes"=>{"email"=>"d#d.com"},
"first_name"=>"test", "last_name"=>"user", "gender"=>"male", "city"=>"la",
"state"=>"ca", "country"=>"usa", "dob"=>"1980-11-20"},
"commit"=>"Create Account", "action"=>"create", "controller"=>"users"}
So it appears that the password and password_confirmation attributes are getting passed correctly. I am wondering if this may have to do with the virtual attribute password I have defined in the user model, but if that is the case I am still not quite sure how to solve this problem. Any help would be greatly appreciated. Let me know if I need to elaborate further.
Here is relevant code for reference:
Controller:
class UsersController < ApplicationController
def new
#user = User.new
#user.build_email
end
def create
if #user = User.create(user_params)
logger.debug "#{#user.errors.messages}"
logger.debug "params: #{params}"
redirect_to :action => "new"
else
logger.debug "#{#user.errors.messages}"
logger.flush
redirect_to :action => "new"
end
end
private
def user_params
params.require(:user).permit(:username, :password, :password_confirmation, :first_name, :last_name, :gender, :dob, :city, :state, :country, :admin_level, email_attributes: [:email])
end
end
Model:
class User < ActiveRecord::Base
has_one :email
validates_presence_of :username, :email, :password
validates_confirmation_of :password, :on => :create
accepts_nested_attributes_for :email
def password_valid?(candidatePass)
candidatePassAndSalt = "#{candidatePass}#{self.salt}"
candidatePasswordDigest = Digest::SHA1.hexdigest(candidatePassAndSalt)
if (candidatePasswordDigest == self.password_digest)
return true
else
return false
end
end
def password
end
def password=(text)
self.salt = Random.new.rand
passAndSalt = "#{text}#{self.salt}"
self.password_digest = Digest::SHA1.hexdigest(passAndSalt)
end
end
View:
<%= form_for #user, url: {action: "create"}, html: {class: "user-creation-form"} do |f| %>
<%= f.text_field :username %>username<br/>
<%= f.password_field :password %>pw<br/>
<%= f.password_field :password_confirmation %>pwcopy<br/>
<%= f.fields_for :email do |email_form| %>
<%= email_form.text_field :email %>email<br />
<% end %>
<%= f.text_field :first_name %>first<br/>
<%= f.text_field :last_name %>last<br/>
<%= f.radio_button :gender, "male" %>
<%= f.label :gender_male, "M" %>
<%= f.radio_button :gender, "female" %>
<%= f.label :gender_female, "F" %><br />
<%= f.text_field :city %>city<br/>
<%= f.text_field :state %>state<br/>
<%= f.text_field :country %>country<br/>
<%= f.date_field :dob %>dob<br/>
<%= f.submit "Create Account" %><br/>
<% end %>
The issue is your empty getter:
def password
end
It always return nil.
2 small additions to the previous answer, which should resolve your issue by the way.
1) If you're using Rails >3 (I assume you are by looking at your user_params method in the controller) you don't have to specify all those password fields and validations.
ActiveRecord automatically includes this ActiveModel method :
has_secure_password
More details at : http://api.rubyonrails.org/classes/ActiveModel/SecurePassword/ClassMethods.html#method-i-has_secure_password
2) If the uncrypted password/password_confirmation are shown in your log files your app is insecure. Add this to your config/application.rb :
config.filter_parameters = [:password, :password_confirmation]
This should not be needed if you are using has_secure_password in your User model.
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