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.
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.
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 have
Model
class User < ActiveRecord::Base
attr_accessible :email
validates :email,
presence: true
serialize :data, ActiveRecord::Coders::Hstore
%w[zipcode first_name].each do |key|
attr_accessible key
define_method(key) do
data && data[key]
end
define_method("#{key}=") do |value|
self.data = (data || {}).merge(key => value)
end
end
end
Controller
class UsersController < ApplicationController
def create
#user = User.find_or_initialize_by_email(params[:user][:email])
if #user.update_attributes(params[:user])
redirect_to :back, notice: "Thanks for sign up!"
else
render "pages/home"
end
end
end
View with client side validation
<%= simple_form_for User.new, validate: true do |f| %>
<%= f.input :email %>
<%= f.input :first_name %>
<%= f.input :zipcode %>
<%= f.button :submit, 'Sign up' %>
<% end %>
First question: I would like to update existing user record but not if param is "" or " ", how to achieve this?
Second question: Should I use create action to do that? Maybe update will be more clear. But this form also create an user object.
Third question: Is any chance to add uniqueness validation to email attribute? Right now my client-side validation do not allow do that.
First: To disallow updating a field with empty strings, set allow_blank: false you could also do allow_nil: false. Add these to your validations in your model.
Second: N/A
Third: Simply add uniqueness: true to your email validation in your model.
Read more here
My question refers to setting up the view and the controller for updating a user's "profile" by confirming the password from the user before updating the attributes. As you've all probably seen a million times before, the user would go to /users/:id/edit, enter a new email in the text field, enter the current password in the password field and click on the submit button to ultimately update the user's email. If the password entered is incorrect then the edit template is rendered again, otherwise the user record is updated with the new email and redirected to :show (or whatever is appropriate for the app). While in the update action I think it makes sense to stick with using the update_attributes method. However the current password value would end up throwing us off.
What I'm really asking though is if there's anything wrong with my approach. I ended up with including a call to password_field_tag for the :current_password field inside the form_for block in order to call update_attributes with params[:user] without making attr_accessible angry. But then I looked up a couple forms in websites that already do this (hulu and destroyallsoftware for example) and they seem to accept the :current_password value in the user hash (assuming they're built with rails). Looking up twitter's settings page it looks like they retrieve this in a separate hash in param (so params[:current_password] instead of params[:user][:current_password]).
Is it wrong to use password_field_tag within form_for? How are these other sites really doing this? The only thing I can think of is that they're either deleting :current_password from the params hash or assigning each attribute individually.
Here is what I basically ended up with:
# /app/models/user.rb
class User < Activerecord::Base
attr_accessible :email, # ...
# ...
end
# /app/views/users/edit.html.erb
<%= form_for #user do |f| %>
# this is stored in params[:user][:email]
<%= f.label :email, 'Your new email' %>
<%= f.text_field :email, type: :email %>
# this is stored in params[:current_password]
<%= label_tag :current_password, 'Re-enter your password to update your email' %>
<%= password_field_tag :current_password %>
<%= f.submit 'Save changes' %>
<% end %>
# /app/controllers/users_controller.rb
# ...
def update
#user = User.find(params[:id])
if #user.authenticate(params[:current_password])
if #user.update_attributes(params[:user])
sign_in #user
flash[:success] = 'Sweet!'
redirect_to #user
else
render :edit
end
else
flash.now[:error] = 'Incorrect password'
render :edit
end
Otherwise, this is the one other way I thought of:
# /app/views/users/edit.html.erb
<%= form_for #user do |f| %>
# this is stored in params[:user][:email]
<%= f.label :email, 'Your new email' %>
<%= f.text_field :email, type: :email %>
# this is stored in params[:user][:current_password]
<%= f.label :current_password, 'Re-enter your password to update your email' %>
<%= f.password_field :current_password %>
<%= f.submit 'Save changes' %>
<% end %>
# /app/controllers/users_controller.rb
# ...
def update
#user = User.find(params[:id])
if #user.authenticate(params[:user][:current_password])
params[:user].delete(:current_password) # <-- this makes me feel a bit uneasy
if #user.update_attributes(params[:user])
sign_in #user
flash[:success] = 'Sweet!'
redirect_to #user
else
render :edit
end
else
flash.now[:error] = 'Incorrect password'
render :edit
end
Or, should I just do this in the controller?:
def update
#user = User.find(params[:id])
if #user.authenticate(params[:user][:current_password])
#user.email = params[:user][:email]
if #user.save
# ...
Any advice is appreciated.
P.S. - Additionally how would you go about refactoring that update action? I tried out a before_filter to authenticate with :current_password and keep only the #update_attributes part in #update, but it got a bit messy. This post is getting long enough though so maybe I'll post this as a separate question if I can not figure it out by next week.
I've recently done something similar to this, except I used a virtual attribute to handle the current_password. You can then add the :current_password attribute to attr_accessible and keep it happy.
I've been working through the Ruby on Rails Tutorial by Michael Hartl. Currently, in order to edit any of the User attributes, the user must confirm their password. Is there any way to update the user attributes without having to do this?
My form looks like this:
<%= form_for #user do |f| %>
<div class="field">
<%= f.label :course1 %><br />
<%= f.text_field :course1 %>
</div>
<div class="actions">
<%= f.submit "Update" %>
</div>
<% end %>"
and my update definition in users_controller.rb looks like this:
def update
if #user.update_attributes(params[:user])
flash[:success] = "Edit Successful."
redirect_to #user
else
#title = "Edit user"
render 'edit'
end
end
Currently, the update_attributes action fails.
Thanks!
To slightly refine Dylan's answer, you need to define that password_changed method which was giving you the error. I used a different name, as I don't care if the password has been changed.
validates :password, :presence => true,
:confirmation => true,
:length => { :within => 6..40 },
:unless => :already_has_password?
private
def already_has_password?
!self.encrypted_password.blank?
end
On your User model, you probably have something along the lines of:
validates_presence_of :password_confirmation
Add an if clause as follows, that way it only checks for the confirmation when the password is actually being changed:
validates_presence_of :password_confirmation, :if => :password_changed?
If you are using bcrypt to encrypt the password, here is the code that worked for me on Rails 4
#--Code for User method
validates :password, presence: true, confirmation: true, :unless => :already_has_password?
#
private
def already_has_password?
!self.password_digest.blank?
end