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.
Related
How can I make an error notice when somebody leaves spaces empty, etc...
I tried enumerous codes and they either didn't work or failed... I even installed a gem but nothing worked until now... please help with ideas/solutions
<div class="jumbotron"
<div class="container">
<h2>Signup</h2>
<%= form_for :user, url: '/users' do |f| %>
Número de Empregado: <br>
<%= f.number_field :NumeroEmpregado %><br>
Primeiro e Último Nome: <br>
<%= f.text_field :nome %><br>
Password: <br>
<%= f.password_field :password %><br>
Confirmação Password: <br>
<%= f.password_field :password_confirmation %><br>
<%= f.submit "Submit" %>
</div>
</div>
<% end %>
Controller:
class UsersController < ApplicationController
def new
end
def create
user = User.new(user_params)
if user.save
session[:user_id] = user.id
redirect_to '/'
else
flash[:error] = 'invalid value'
end
end
private
def user_params
params.require(:user).permit(:NumeroEmpregado, :nome, :password, :password_confirmation)
end
end
Rails only can validate when you submitted your form. In this case, you can check out this guide.
In other case, if you want immediately show validate message when user enter into input, you must validate it by using Javascript. You also can use Jquery validation plugin to do that.
Hope its help :)
you should use jquery validations if you want to check if the field is empty type validations before submit https://jqueryvalidation.org/documentation/ follow this link.
Or if you are ok with validating fields on submit then in create or update action of your controller you can check from the params if that field is empty. then you can show flash messages like this
flash[:notice] = 'valid value'
flash[:error] = 'invalid value'
Or you can add model level validations on that fields http://guides.rubyonrails.org/active_record_validations.html here
You can use the Active Record Validations.
If you have a model called User, then you can specify which attributes to validate for presence using the validates method and setting the presence to true, this will check if the field on the form has been filled, also if has only whitespaces.
For instance:
class User < ApplicationRecord
validates :nome, presence: true
...
For your controller:
def create
user = User.new(user_params)
if user.save
session[:user_id] = user.id
redirect_to '/'
else
flash[:error] = 'invalid value'
redirect_to new_user_path
end
end
Redirect to the new user path with flash[:error] as "invalid value".
For your view:
<% if flash[:error] %>
<div class="error">
<%= flash[:error] %>
</div>
<% end %>
I'm need to check a bunch of conditions in a controller method. 1) it's a mess and 2) it's not even hitting the right redirects.
def password_set_submit
password_check = /^(?=.*[a-z]{1,})(?=.*[A-Z]{1,})(?=.*\d{1,}){8,}.+$/
#user = User.find(session[:id])
if params[:password] && params[:password_confirmation] && params[:username] && params[:old_password]
if params[:password] == params[:password_confirmation] && params[:password] =~ password_check
# do some api stuff here
if #user.save
flash[:success] = 'Password updated.'
redirect_to login_path and return
end
end
if params[:password] != params[:password_confirmation]
flash[:error] = 'Passwords did not match.'
redirect_to password_set_path and return
end
if params[:password] == params[:password_confirmation] && params[:password] !~ password_check
flash[:error] = 'Passwords did not match password criteria.'
redirect_to password_set_path and return
end
end
else
flash[:error] = 'Please fill all inputs.'
redirect_to password_set_path and return
end
end
This needs to do the following:
1) If less than four params submitted, redirect and display 'Fill all inputs'
2) If password and password confirmation don't match each other, redirect and display 'Password did not match'
3) If password and password confirmation match each other but do not match criteria, redirect and display 'Passwords did not match criteria'
4) If password and password confirmation match each other and match criteria, make api call and redirect to login
I'm out of if/else ideas and I hope cleaning this up will help me nail the redirects correctly.
The Rails way to this is by using model validations.
class User < ActiveRecord::Base
validates :password, confirmation: true, presence: true# password must match password_confirmation
validates :password_confirmation, presence: true # a password confirmation must be set
end
If we try to create or update a user without a matching pw / pw confirmation the operation will fail.
irb(main):006:0> #user = User.create(password: 'foo')
(1.5ms) begin transaction
(0.2ms) rollback transaction
=> #<User id: nil, password: "foo", password_confirmation: nil, created_at: nil, updated_at: nil>
irb(main):007:0> #user.errors.full_messages
=> ["Password confirmation can't be blank"]
irb(main):008:0>
However
When dealing with user passwords you should NEVER NEVER NEVER store them in the database in plain text!
Since most users reuse a common password you might also be compromising their email, bank account etc. You could potentially be held financially and legally responsible and it can destroy your career.
The answer is to use an encrypted password. Since this is incredibly easy to get wrong Rails has something called has_secure_password which encrypts and validates passwords.
The first thing you want to do is to remove the password and password_confirmation columns from your users database.
Add a password_digest column. And then add has_secure_password to your model.
class User < ActiveRecord::Base
PASSWORD_CHECK = /^(?=.*[a-z]{1,})(?=.*[A-Z]{1,})(?=.*\d{1,}){8,}.+$/
has_secure_password
validates :password, format: PASSWORD_CHECK
end
This will automatically add validations for the password, confirmation and getters and setters for password and password_confirmation.
To check if the old password is correct we would do:
#user = User.find(session[:id]).authenticate(params[:old_password])
# user or nil
This is an example of the Rails way of doing it:
class UsersController
# We setup a callback that redirects to the login if the user is not logged in
before_action :authenticate_user!, only: [ :password_set_submit ]
def password_set_submit
# We don't want assign the the old_password to user.
unless #user.authenticate(params[:old_password])
# And we don't want to validate on the model level here
# so we add an error manually:
#user.errors.add(:old_password, 'The current password is not correct.')
end
if #user.update(update_password_params)
redirect_to login_path, notice: 'Password updated.'
else
# The user failed to update, so we want to render the form again.
render :password_set, alert: 'Password could not be updated.'
end
end
private
# Normally you would put this in your ApplicationController
def authenticate_user!
#user = User.find(session[:id])
unless #user
flash.alert('You must be signed in to perform this action.')
redirect_to login_path
end
end
# http://guides.rubyonrails.org/action_controller_overview.html#strong-parameters
def update_password_params
params.require(:user).permit(:password, :password_confirmation)
end
end
Notice how the logic in our action is much much simpler? Either the user is updated and we redirect or it is invalid and we re-render the form.
Instead of creating one flash message per error we display the errors on the form:
<%= form_for(#user, url: { action: :password_set_submit}, method: :patch) do |f| %>
<% if #user.errors.any? %>
<div id="error_explanation">
<h2>Your password could not be updated:</h2>
<ul>
<% #user.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="row">
<%= f.label :password, 'New password' %>
<%= f.password_field_tag :password %>
</div>
<div class="row">
<%= f.label :password_confirmation %>
<%= f.password_field_tag :password_confirmation %>
</div>
<div class="row">
<p>Please provide your current password for confirmation</p>
<%= f.label :old_password, 'Current password' %>
<%= f.password_field_tag :old_password %>
</div>
<%= f.submit 'Update password' %>
<% end %>
I would remove all code related to this password reset from the controller and put into its own model User::PasswordReset:
# in app/models/user/password_reset.rb
class User::PasswordReset
attr_reader :user, :error
PASSWORD_REGEXP = /^(?=.*[a-z]{1,})(?=.*[A-Z]{1,})(?=.*\d{1,}){8,}.+$/
def initialize(user_id)
#user = User.find(user_id)
end
def update(parameters)
if parameters_valid?(parameters)
# do some api stuff here with `user` and `parameters[:password]`
else
false
end
end
private
def valid?
error.blank?
end
def parameters_valid?(parameters)
parameter_list_valid(parameters.keys) &&
password_valid(params[:password], params[:password_confirmation])
end
def parameter_list_valid(keys)
mandatory_keys = [:password, :password_confirmation, :username, :old_password]
unless mandatory_keys.all? { |key| keys.include?(key) }
#error = 'Please fill all inputs.'
end
valid?
end
def password_valid(password, confirmation)
if password != confirmation
#error = 'Passwords did not match.'
elsif password !~ PASSWORD_REGEXP
#error = 'Passwords did not match password criteria.'
end
valid?
end
end
That would allow to change the controller's method to something simpler like this:
def password_set_submit
password_reset = User::PasswordReset.new(session[:id])
if password_reset.update(params)
flash[:success] = 'Password updated.'
redirect_to(login_path)
else
flash[:error] = password_reset.error
redirect_to(password_set_path)
end
end
Once you did this refactoring it should be much easier to find problems in your conditions and to extend your code.
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.
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'm new to Ruby on Rails, and as my first project I'm creating a beta sign-up page for my startup. I just want to save the users email address to a database for future use, and I am not able to persist any data into my database. I can add emails through the rails console, but my form/controller are not working. What's wrong?
User Model:
class User < ActiveRecord::Base
attr_accessible :email
before_save { |user| user.email = email.downcase }
VALID_EMAIL_REGEX = /\A[\w+\-.]+#[a-z\d\-.]+\.[a-z]+\z/i
validates :email, presence: true, format: { with: VALID_EMAIL_REGEX }, uniqueness:
{case_sensitive: false }
end
User Controller:
class UsersController < ApplicationController
def index
end
def create
User.create params[:email]
redirect_to :back, :notice => "Success!"
end
end
Home page HTML:
<h1>######</h1>
<p>Welcome to #####! Give us your email address, and we'll keep you informed on our
latest happenings.
You'll also be placed on the list for our private alpha and beta testings.</p>
<%= render 'form' %>
Form Partial:
<%= form_for User.new do |f| %>
<%= f.label :email %>
<%= f.text_field :email %>
<%= f.submit %>
<% end %>
Thanks!
First, create the user with params[:user] not params[:email]. If you want just the email address, put params[:user][:email].
This is because params is a nested hash and looks like this:
params[:user] = { email: "name#example.com" }.
Second, use conditional logic in case the email address does not pass validation and doesn't save.
#user = User.new(params[:user])
if #user.save
redirect_to :back, :notice => "Success!"
else
# display error messages
end
or if you want to get verbose:
#user = User.new(:email => params[:user][:email])
This is wrong:
User.create params[:email]
It should be:
User.create params[:user]