-
-
Save namelessjon/1039058 to your computer and use it in GitHub Desktop.
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 |
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.
@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.
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
What about using the Bcrypt property from dm-types? Could that remove the need for the
#crypt_password
and#crypted_pass
methods?Also could
#reset_password?
be written as:If I was worried about future devise compatibility, I might recommend changing crypted_password to encrypted_password.
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.