Skip to content

Instantly share code, notes, and snippets.

@jacortinas
Created July 13, 2012 09:50
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jacortinas/3103980 to your computer and use it in GitHub Desktop.
Save jacortinas/3103980 to your computer and use it in GitHub Desktop.
The difference between testing a small authentication file with BCrypt cost set to normal, then set to min.
# app/models/user/authentication.rb
require 'bcrypt'
class User
# Password authentication related methods and utilities.
module Authentication
extend ActiveSupport::Concern
module ClassMethods
# Authenticate the login credentials of a user.
#
# @param username_or_email [String] the username or email.
# @param password [String] the password.
#
# @return [User, NilClass] the user if authenticated, otherwise nil.
def authenticate(username_or_email, password)
user = find_by_username_or_email(username_or_email)
user if user && user.correct_password?(password)
end
# Find a user by username or email, case insensitive.
#
# @param username_or_email [String]
#
# @return [User, NilClass] if found, matching user otherwise nil.
def find_by_username_or_email(username_or_email)
if username_or_email.present?
# builds a `LOWER('blah')` arel node.
value = arel_table.lower(username_or_email)
# build the where statements as lower case comparisons as well.
username_query = arel_table[:username].lower.eq(value)
email_query = arel_table[:email].lower.eq(value)
where(username_query.or(email_query)).first
end
end
end # module ClassMethods
# Whether the unencrypted password matches the stored password digest.
#
# @param unencrypted_password [String] unencrypted plain text password.
#
# @return [boolean] true if the password is correct, otherwise false.
def correct_password?(unencrypted_password)
BCrypt::Password.new(password_digest) == unencrypted_password
end
# @return [String] the user's password.
def password; @password; end # avoiding an attr_reader 'cause YARD pukes.
# Encrypts an unencrypted password and assigns it the password_digest
# attribute.
#
# @param unencrypted_password [String] unencrypted plain text password.
def password=(unencrypted_password)
@password = unencrypted_password
unless unencrypted_password.blank?
self.password_digest = BCrypt::Password.create(unencrypted_password)
end
end
end # module Authentication
end # class User
# spec/models/user/authentication.rb
require 'spec_helper'
describe User::Authentication do
subject { User.new }
describe ".authenticate" do
it "tries to find the user" do
User.should_receive(:find_by_username_or_email)
User.authenticate('blah@blah.com', 'blahblah')
end
it "checks if the password is correct" do
user = double(:user)
User.stub(:find_by_username_or_email).and_return user
user.should_receive(:correct_password?)
User.authenticate('blah@blah.com', 'blahblah')
end
it "returns the found user if authenticated" do
user = double(:user)
User.stub(:find_by_username_or_email).and_return user
user.stub(:correct_password?).and_return true
User.authenticate('blah@blah.com', 'blahblah')
end
it "returns nil if the user doesn't exists" do
User.stub(:find_by_username_or_email).and_return nil
User.authenticate('blah@blah.com', 'blahblah')
end
it "returns nil if the password is incorrect" do
user = double(:user)
User.stub(:find_by_username_or_email).and_return user
user.stub(:correct_password?).and_return false
User.authenticate('blah@blah.com', 'blahblah')
end
end
describe ".find_by_username_or_email" do
before :all do
@user = FactoryGirl.create :user, email: 'kramer@vulume.com', username: 'cosmo'
end
it "finds a user by username case insensitively" do
User.find_by_username_or_email('cosmo').should be == @user
User.find_by_username_or_email('COSMO').should be == @user
end
it "finds a user by email case insensitively" do
User.find_by_username_or_email('kramer@vulume.com').should be == @user
User.find_by_username_or_email('KRAMER@VULUME.com').should be == @user
end
it "is nil if nothing is found" do
User.find_by_username_or_email('blahblahblah').should be_nil
end
it "does not query the database if a blank value is passed in" do
method = :find_by_username_or_email
User.should_not query_the_database.when_calling(method).with('')
User.should_not query_the_database.when_calling(method).with(nil)
end
end
describe "#correct_password" do
it "is true if the passed in password matches the password digest" do
subject.password = 'blah'
subject.correct_password?('blah').should be_true
end
it "false if the password does not match" do
subject.password = 'omfg'
subject.correct_password?('notthesame').should be_false
end
end
describe "#password=" do
it "sets the password attribute" do
subject.password = 'blah'
subject.password.should be == 'blah'
end
it "sets the password digest" do
subject.should_receive(:password_digest=)
subject.password = 'blah'
end
it "does not set the password digest if the unencrypted password is blank" do
subject.should_not_receive(:password_digest=)
subject.password = ''
subject.password = nil
end
end
end
# config/initializers/bcrypt.rb
require 'bcrypt'
# BCrypt was made to be slow, by lowering the cost during testing, we are
# giving up security (meh) but *literally* shaving seconds off of the total
# time for the test suite to run.
if Rails.env.test? || Rails.env.cucumber?
silence_warnings do
BCrypt::Engine::DEFAULT_COST = BCrypt::Engine::MIN_COST
end
end
# With default cost of 10 passes
Finished in 0.66575 seconds
# With minimum allowed cost of 4 passes
Finished in 0.10229 seconds
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment