密码验证何时触发不应触发?

时间:2014-06-08 06:17:01

标签: ruby-on-rails validation activerecord ruby-on-rails-4

为用户使用密码重置机制。密码长度验证正在触发,我试图了解原因。

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 %>

错误是:

Validation failed: Password is too short (minimum is 6 characters)

抛出它的行在create_password_reset_token方法中:

self.update_attributes!(password_reset_token: SecureRandom.urlsafe_base64, password_reset_sent_at: Time.zone.now)

为什么验证会在此处触发?我没有用密码本身做任何事情。我只是在用户记录中创建一个令牌和时间。

将验证更改为on: :create使其不会触发。问题是,用户可以将密码重置为少于六个字符的内容。

澄清

要明确的是,操作的顺序是:

  1. 用户点击“我忘记了密码”链接。
  2. 他们被带到password_reset_controller / new.html.erb。此表单有一个字段:电子邮件地址。他们输入电子邮件并提交。
  3. Controller检查该用户是否存在。如果是,则告诉模型生成password_reset_token。
  4. Controller然后命令将电子邮件发送给用户,并使用包含该令牌的URL。
  5. 用户点击该网址。如果令牌有效,他们将被带到edit.html.erb,他们会输入新的电子邮件及其确认。
  6. 控制器调用reset_password方法,该方法实际上会重置用户的密码。
  7. 目前,验证会在输入电子邮件并单击“提交”后触发第2步。

2 个答案:

答案 0 :(得分:0)

你的create_password_reset_token正在调用update_attributes,它将触发用户模型中每个字段的验证,从而触发密码验证,因为它没有当前的一套

你需要

1)对那些特定字段使用update_attribute并且不会触发验证

2)在模型中添加一些password_reset字段或枚举,并在单击密码重置按钮时将其设置为true,然后在用户模型中执行类似操作

has_secure_password :validations => false
validates :password, length: {minimum: 6}, unless: -> { user_password_reset? }

3)使用设计宝石来照顾你

更新

试试这个

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

答案 1 :(得分:0)

我通过在密码验证中添加Proc语句来解决这个问题,如下所示:

validates :password, length: { minimum: 6 }, unless: Proc.new { |a| !a.password_reset_token.nil? }

现在验证在用户创建和密码重置期间运行,但在设置密码重置令牌的时间间隔内不运行。所有测试都在通过。