Module: ZeroAuth::Model::Password
- Defined in:
- lib/zero_auth/model/password.rb
Overview
The Password module includes the following features:
- Password salting and hashing using
BCrypt. - Instance level authentication method.
...yup, that's it. The idea is to provide a quick, secure way to store encrypted passwords. It is almost identical to Rails' own has_secure_password method, with the only exception being ZeroAuth uses a unique salt stored alongside and used to compute a password's hash.
Requirement
Your object must be able to write to the password_salt and the
password_hash attributes (we handle the password attribute), so you
might want to do something like this in a Rails migration:
change_table :users do |t|
t.string :password_salt, null: false, default: ""
t.string :password_hash, null: false, default: ""
end
...or something like this using MongoMapper:
class User
include MongoMapper::Document
include ZeroAuth::Model::Password
key :password_salt, String
key :password_hash, String
end
Usage
class User < ActiveRecord::Base
include ZeroAuth::Model::Password
end
user = User.create(email: '[email protected]', password: 'password')
user.has_password?('password') # => true
user.has_password?('pa$$w0rD') # => false
user.authenticate!('password') # => true
user.authenticate!('pa$$word') # => raises ZeroAuth::Unauthorized
Implementing Validations
This module provides a #requires_password? helper method that, by
default, simply checks whether this object is a #new_record? (if
we can check that) or if the #password attributes is empty?.
Again, with ActiveRecord or ActiveModel as an example, you can utilize it like this:
class User < ActiveRecord
include ZeroAuth::Model::Password
if: :requires_password? do |pw|
pw.validates :password, length: {in: 8..40}, confirmation: true
end
end
You can also simply override the #requires_password? method to require
validation when an :old_password attribute is present:
attr_accesor :old_password
def requires_password?
super || old_password.present?
end
Customize Authentication Class Method
The Password module leaves record retreival and the
exact authentication tree up to the developer, but implementing a
custom class method is trivial. An example using ActiveRecord might look
something like this:
class User < ActiveRecord::Base
include ZeroAuth::Model::Password
def self.authenticate(email, password)
begin
self.authenticate!(email, password)
rescue ZeroAuth::Unauthorized
end
end
def self.authenticate!(email, password)
begin
record = self.find_by!(email: email)
record.authenticate!(password) && record
rescue ActiveRecord::RecordNotFound
fail ZeroAuth::Unauthorized
end
end
end
Note the use of the Unauthorized exception. This is a custom error class provided to allow a standard way for your application to capture exceptions regarding authentication and authorization.
The implementation above is certainly a personal perference, but the logic, again, is left up to you.
Instance Method Summary collapse
-
#authenticate!(test_password) ⇒ Nil
A helper method that takes a given password and raises a Unauthorized error if the call to
has_password?fails. -
#has_password?(test_password) ⇒ Boolean
Compares the given password with the current password attributes using Utils.compare_password.
-
#password=(unencrypted_password) ⇒ Mixed
If the given password is not
nil?, generates apassword_saltand encrypts the given password into thepassword_hashusing that salt. -
#requires_password? ⇒ Boolean
Checks that the object is a
#new_record?(if we can) or that thepasswordattribute is not empty.
Instance Method Details
#authenticate!(test_password) ⇒ Nil
A helper method that takes a given password and raises a
Unauthorized error if the call to has_password? fails.
168 169 170 |
# File 'lib/zero_auth/model/password.rb', line 168 def authenticate!(test_password) has_password?(test_password) || fail(ZeroAuth::Unauthorized) end |
#has_password?(test_password) ⇒ Boolean
Compares the given password with the current password attributes using Utils.compare_password.
178 179 180 181 |
# File 'lib/zero_auth/model/password.rb', line 178 def has_password?(test_password) return false unless !ZeroAuth::Utils.empty?(password_hash) ZeroAuth::Password.compare(password_hash, password_salt, test_password) end |
#password=(unencrypted_password) ⇒ Mixed
If the given password is not nil?, generates a password_salt and
encrypts the given password into the password_hash using that salt.
The salt and hash are generated using Password.generate_salt
and Utils.create_password.
138 139 140 141 142 143 144 145 146 147 148 |
# File 'lib/zero_auth/model/password.rb', line 138 def password=(unencrypted_password) if unencrypted_password.nil? @password = nil self.password_salt = nil self.password_hash = nil else @password = unencrypted_password self.password_salt = ZeroAuth::Password.generate_salt self.password_hash = ZeroAuth::Password.create(password, password_salt) end end |
#requires_password? ⇒ Boolean
Checks that the object is a #new_record? (if we can) or that the
password attribute is not empty.
155 156 157 158 |
# File 'lib/zero_auth/model/password.rb', line 155 def requires_password? is_new = (respond_to?(:new_record?) && new_record?) is_new || !ZeroAuth::Utils.empty?(password) end |