Our Blog

Ongoing observations by End Point people

Custom validation with authlogic: Password can't be repeated.

By Marina Lohova · Friday, December 7, 2012

Tags: rails

I recently worked on a small security system enhancement for one of my projects: the user must not be able to repeat his or her password for at least ten cycles of change. Here is a little recipe for all the authlogic users out there.

We will store ten latest passwords in the users table.

def self.up
    change_table :users do |t|
      t.text    :old_passwords
    end
  end

The database value will be serialized and deserialized into Ruby array.

class User
  serialize :old_passwords, Array
end

If the crypted password field has changed, the current crypted password and its salt are added to the head of the array. The array is then sliced to hold only ten passwords.

def update_old_passwords
  if self.errors.empty? and send("#{crypted_password_field}_changed?")
    self.old_passwords ||= []
    self.old_passwords.unshift({:password => send("#{crypted_password_field}"), :salt =>  send("#{password_salt_field}") })
    self.old_passwords = self.old_passwords[0, 10]
  end
end

The method will be triggered after validation before save.

class User
  after_validation :update_old_passwords
end

Next, we need to determine if the password has changed, excluding the very first time when the password is set on the new record.

class User < ActiveRecord::Base
  def require_password_changed?
    !new_record? && password_changed?
  end
end

The validation method itself is implemented below. The idea is to iterate through the stored password salts and encrypt the current password with them using the authlogic mechanism, and then check if the resulting crypted password is already present in the array.

def password_repeated?
  return if self.password.blank?
  found = self.old_passwords.any? do |old_password|
    args = [self.password, old_password[:salt]].compact
    old_password[:password] == crypto_provider.encrypt(args)
  end
  self.errors.add_to_base "New password should be different from the password used last 10 times." if found
end

Now we can plug the validation into the configuration.

class User < ActiveRecord::Base
  acts_as_authentic do |c|
    c.validate :password_repeated?, :if => :require_password_changed?
  end
end

Done!

Comments

Archive