diff --git a/app/models/user.rb b/app/models/user.rb index 6fc0bd1..0802e10 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -9,9 +9,11 @@ # created_at :datetime # updated_at :datetime # +require 'digest' class User < ActiveRecord::Base - attr_accessible :name, :email + attr_accessor :password + attr_accessible :name, :email, :password, :password_confirmation email_regex = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i @@ -22,4 +24,39 @@ class User < ActiveRecord::Base :format => { :with => email_regex }, :uniqueness => { :case_sensitive => false } + validates :password, :presence => true, + :confirmation => true, + :length => { :within => 6..40 } + + before_save :encrypt_password + + def has_password?(submitted_password) + encrypted_password == encrypt(submitted_password) + end + + def self.authenticate(email, submitted_password) + user = find_by_email(email) + return nil if user.nil? + return user if user.has_password?(submitted_password) + end + + private + + def encrypt_password + self.salt = make_salt if new_record? + self.encrypted_password = encrypt(password) + end + + def encrypt(string) + secure_hash("#{salt}--#{string}") + end + + def make_salt + secure_hash("#{Time.now.utc}--#{password}") + end + + def secure_hash(string) + Digest::SHA2.hexdigest(string) + end + end diff --git a/db/migrate/20110301070758_add_password_to_users.rb b/db/migrate/20110301070758_add_password_to_users.rb new file mode 100644 index 0000000..0621dc1 --- /dev/null +++ b/db/migrate/20110301070758_add_password_to_users.rb @@ -0,0 +1,9 @@ +class AddPasswordToUsers < ActiveRecord::Migration + def self.up + add_column :users, :encrypted_password, :string + end + + def self.down + remove_column :users, :encrypted_password + end +end diff --git a/db/migrate/20110301074852_add_salt_to_users.rb b/db/migrate/20110301074852_add_salt_to_users.rb new file mode 100644 index 0000000..11e8f53 --- /dev/null +++ b/db/migrate/20110301074852_add_salt_to_users.rb @@ -0,0 +1,9 @@ +class AddSaltToUsers < ActiveRecord::Migration + def self.up + add_column :users, :salt, :string + end + + def self.down + remove_column :users, :salt + end +end diff --git a/db/schema.rb b/db/schema.rb index a9c02b5..62b7936 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,13 +10,15 @@ # # It's strongly recommended to check this file into your version control system. -ActiveRecord::Schema.define(:version => 20110228131804) do +ActiveRecord::Schema.define(:version => 20110301074852) do create_table "users", :force => true do |t| t.string "name" t.string "email" t.datetime "created_at" t.datetime "updated_at" + t.string "encrypted_password" + t.string "salt" end add_index "users", ["email"], :name => "index_users_on_email", :unique => true diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 5570c7f..f807a7a 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -2,7 +2,10 @@ describe User do before(:each) do - @attr = { :name => "Example User", :email => "user@example.com" } + @attr = { :name => "Example User", + :email => "user@example.com", + :password => "foobar", + :password_confirmation => "foobar" } end it "should create a new instance givien valid attributes" do @@ -59,4 +62,66 @@ user_with_duplicate_email_address.should_not be_valid end + describe "password validations" do + it "should require a pssword" do + User.new(@attr.merge(:password => "", :password_confirmation => "")).should_not be_valid + end + + it "should require a matching password confirmation" do + User.new(@attr.merge(:password_confirmation => "invalid")).should_not be_valid + end + + it "should reject short passwords" do + short = "a" * 5 + hash = @attr.merge(:password => short, :password_confirmation => short) + User.new(hash).should_not be_valid + end + + it "should reject long passwords" do + long = "a" * 41 + hash = @attr.merge(:password => long, :password_confirmation => long) + User.new(hash).should_not be_valid + end + end + + describe "password encryption" do + before(:each) do + @user = User.create!(@attr) + end + + it "should have an encryted password attribute" do + @user.should respond_to(:encrypted_password) + end + + it "should set the encrypted password" do + @user.encrypted_password.should_not be_blank + end + + describe "has_password? method" do + it "should be true if the passwords match" do + @user.has_password?(@attr[:password]).should be_true + end + + it "sould be false if passwords do not match" do + @user.has_password?("invalid").should be_false + end + end + + describe "authenticate method" do + it "should return nil on email/password mismatch" do + wrong_password_user = User.authenticate(@attr[:email], "wrongpass") + wrong_password_user.should be_nil + end + + it "should return nil on for an email address with no user" do + not_found_user = User.authenticate("bar@foo.com", @attr[:password]) + not_found_user.should be_nil + end + + it "should return the user on email/password match" do + matching_user = User.authenticate(@attr[:email], @attr[:password]) + matching_user.should == @user + end + end + end end