Skip to content

Instantly share code, notes, and snippets.

@namelessjon
Created June 21, 2011 22:14
Show Gist options
  • Save namelessjon/1039058 to your computer and use it in GitHub Desktop.
Save namelessjon/1039058 to your computer and use it in GitHub Desktop.
Example user with a BCrypt password
require 'bcrypt'
class User
include DataMapper::Resource
attr_accessor :password, :password_confirmation
timestamps :at
property :id, Serial
property :username, String, :length => 4..30, :unique => true, :required => true
property :crypted_pass, String, :length => 60..60, :required => true, :writer => :protected
property :email, String, :length => 5..200, :required => true,
:format => :email_address
validates_presence_of :password, :password_confirmation, :if => :password_required?
validates_confirmation_of :password, :if => :password_required?
before :valid?, :crypt_password
# check validity of password if we have a new resource, or there is a plaintext password provided
def password_required?
new? or password
end
def reset_password(password, confirmation)
update(:password => password, :password_confirmation => confirmation)
end
# Hash the password using BCrypt
#
# BCrypt is a lot more secure than a hash made for speed such as the SHA algorithm. BCrypt also
# takes care of adding a salt before hashing. The whole thing is encoded in a string 60 bytes long.
def crypt_password
self.crypted_pass = BCrypt::Password.create(password) if password
end
# Prepare a BCrypt hash from the stored password, overriding the default reader
#
# return the `:no_password` symbol if the property has no content. This is for
# the safety of the authenticate method. It's easy to pass a nil password to
# that method, but passing a specific symbol takes effort.
def crypted_pass
pass = super
if pass
BCrypt::Password.new(pass)
else
:no_password
end
end
def authenticate(password)
crypted_pass == password
end
def self.authenticate(username, password)
un = username.to_s.downcase
u = first(:conditions => ['lower(email) = ? OR lower(username) = ?', un, un])
if u && u.authenticate(password)
u
else
nil
end
end
end
@namelessjon
Copy link
Author

What about using the Bcrypt property from dm-types? Could that remove the need for the #crypt_password and #crypted_pass methods?

I had some reason for not using the BCryptHash type at the time this code was originally written. I think it might have been as simple as at the time the model was added, I was only using that type and didn't want the extra gem dependency for what is very few lines of code.

Also could #reset_password? be written as:

def reset_password(password, confirmation)
  update(:password_reset => true, :password => password, :password_confirmation => confirmation)
end

Yes, it could.

If I was worried about future devise compatibility, I might recommend changing crypted_password to encrypted_password.

I've never used devise. It's always seemed like a massive sledgehammer compared to the nut of a few methods needed to implement a simple auth scheme. Which is all I've ever needed.

This comes up often enough I wonder if we should have a semi-official example of how to do this somewhere? Or even a module that you can include in a User class.

I think an example is better than a module. Things like the self.authenticate method are almost certain to be written to taste. I could look into writing up a simple example for the DataMapper blog knowtheory has lying around or for the DataMapper site.

@dkubb
Copy link

dkubb commented Aug 14, 2011

@namelessjon I think you're right on all counts :)

If you want to write up something for the DM site that would be awesome. I know I've had to write this kind code lots of times before, and I bet it comes up fairly often.

@mcansky
Copy link

mcansky commented Sep 6, 2011

for some reason the reset password method didn't work like that for me, so here is what I did :

  def reset_password(password, confirmation)
    if (password == confirmation)
      self.password_reset = true
      self.password = password
      self.password_confirmation = confirmation
      self.crypt_password
    end
  end

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment