-
-
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_passwordand#crypted_passmethods?
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_passwordand#crypted_passmethods?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.