-
-
Save bdimcheff/263040 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# include this in application controller | |
module Authentication | |
protected | |
# Inclusion hook to make #current_user and #signed_in? | |
# available as ActionView helper methods. | |
def self.included(base) | |
base.send :helper_method, :current_user, :signed_in?, :authorized? if base.respond_to? :helper_method | |
end | |
# Returns true or false if the user is signed in. | |
# Preloads @current_user with the user model if they're signed in. | |
def signed_in? | |
!!current_user | |
end | |
# Accesses the current user from the session. | |
# Future calls avoid the database because nil is not equal to false. | |
def current_user | |
@current_user ||= (sign_in_from_session || sign_in_from_basic_auth) unless @current_user == false | |
end | |
# Store the given user id in the session. | |
def current_user=(new_user) | |
session[:user_id] = new_user ? new_user.id : nil | |
@current_user = new_user || false | |
end | |
# Check if the user is authorized | |
# | |
# Override this method in your controllers if you want to restrict access | |
# to only a few actions or if you want to check if the user | |
# has the correct rights. | |
# | |
# Example: | |
# | |
# # only allow nonbobs | |
# def authorized? | |
# current_user.name != "bob" | |
# end | |
# | |
def authorized?(action=nil, resource=nil, *args) | |
signed_in? | |
end | |
# Filter method to enforce a sign_in requirement. | |
# | |
# To require sign_ins for all actions, use this in your controllers: | |
# | |
# before_filter :sign_in_required | |
# | |
# To require sign_ins for specific actions, use this in your controllers: | |
# | |
# before_filter :sign_in_required, :only => [ :edit, :update ] | |
# | |
# To skip this in a subclassed controller: | |
# | |
# skip_before_filter :sign_in_required | |
# | |
def authenticate | |
authorized? || access_denied | |
end | |
# Redirect as appropriate when an access request fails. | |
# | |
# The default action is to redirect to the sign_in screen. | |
# | |
# Override this method in your controllers if you want to have special | |
# behavior in case the user is not authorized | |
# to access the requested action. For example, a popup window might | |
# simply close itself. | |
def access_denied | |
respond_to do |format| | |
format.html do | |
store_location | |
redirect_to new_session_path | |
end | |
# format.any doesn't work in rails version < http://dev.rubyonrails.org/changeset/8987 | |
# you may want to change format.any to e.g. format.any(:js, :xml) | |
format.any do | |
request_http_basic_authentication 'Web Password' | |
end | |
end | |
end | |
# Store the URI of the current request in the session. | |
# | |
# We can return to this location by calling #redirect_back_or_default. | |
def store_location | |
session[:return_to] = request.request_uri | |
end | |
# Redirect to the URI stored by the most recent store_location call or | |
# to the passed default. Set an appropriately modified | |
# after_filter :store_location, :only => [:index, :new, :show, :edit] | |
# for any controller you want to be bounce-backable. | |
def redirect_back_or_default(default) | |
redirect_to(session[:return_to] || default) | |
session[:return_to] = nil | |
end | |
# Called from #current_user. First attempt to sign_in by the user id stored in the session. | |
def sign_in_from_session | |
if session[:user_id] | |
self.current_user = User.find_by_id(session[:user_id]) | |
end | |
end | |
# Called from #current_user. Now, attempt to sign_in by basic authentication information. | |
def sign_in_from_basic_auth | |
authenticate_with_http_basic do |email, password| | |
self.current_user = User.authenticate(email, password) | |
end | |
end | |
# This is ususally what you want; resetting the session willy-nilly wreaks | |
# havoc with forgery protection, and is only strictly necessary on sign_in. | |
# However, **all session state variables should be unset here**. | |
def sign_out_keeping_session! | |
# Kill server-side auth cookie | |
@current_user = false # not signed in, and don't do it for me | |
session[:user_id] = nil # keeps the session but kill our variable | |
# explicitly kill any other session variables you set | |
end | |
# The session should only be reset at the tail end of a form POST -- | |
# otherwise the request forgery protection fails. It's only really necessary | |
# when you cross quarantine (signed-out to signed-in). | |
def sign_out_killing_session! | |
sign_out_keeping_session! | |
reset_session | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# factory girl factories | |
Factory.define(:user) do |u| | |
u.sequence(:email) { |n| "john#{n}@doe.com" } | |
u.password "testing" | |
u.password_confirmation "testing" | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<%- form_for @user do |form| -%> | |
<%= form.error_messages %> | |
<ul> | |
<li> | |
<%= form.label :email %> | |
<%= form.text_field :email %> | |
</li> | |
<li> | |
<%= form.label :password %> | |
<%= form.password_field :password %> | |
</li> | |
<li> | |
<%= form.label :password_confirmation, 'Confirm Password' %> | |
<%= form.password_field :password_confirmation %> | |
</li> | |
<li class="submit"> | |
<%= submit_tag 'Sign Up', :disable_with => 'Signing you up...' %> | |
or <%= link_to 'sign in', new_session_path %> | |
</li> | |
</ul> | |
<%- end -%> | |
<%- form_tag session_path do -%> | |
<ul> | |
<li> | |
<%= label_tag 'email', 'Email' %> | |
<%= text_field_tag :email %> | |
</li> | |
<li> | |
<%= label_tag 'password', 'Password' %> | |
<%= password_field_tag :password %> | |
</li> | |
<li class="submit"> | |
<%= submit_tag 'Sign In', :disable_with => 'Signing you in...' %> | |
or <%= link_to 'sign up', new_user_path %> | |
</li> | |
</ul> | |
<%- end -%> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class UsersController < ApplicationController | |
def new | |
@user = User.new | |
render :layout => 'sessions' | |
end | |
def create | |
@user = User.new(params[:user]) | |
if @user.save | |
self.current_user = @user | |
redirect_to root_path | |
else | |
render 'new', :layout => 'sessions' | |
end | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class ActiveSupport::TestCase | |
# clear each collection in setup | |
def setup | |
Dir[Rails.root + 'app/models/**/*.rb'].each do |model_path| | |
model_name = File.basename(model_path).gsub(/\.rb$/, '') | |
klass = model_name.classify.constantize | |
klass.collection.clear | |
end | |
end | |
# stuff removed for brevity | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# uses bcrypt-ruby gem for password hashing be sure to config.gem it | |
require 'digest/sha1' | |
class User | |
include MongoMapper::Document | |
key :email, String, :required => true | |
key :crypted_password, String | |
key :reset_password_code, String | |
key :reset_password_code_until, Time | |
RegEmailName = '[\w\.%\+\-]+' | |
RegDomainHead = '(?:[A-Z0-9\-]+\.)+' | |
RegDomainTLD = '(?:[A-Z]{2}|com|org|net|gov|mil|biz|info|mobi|name|aero|jobs|museum)' | |
RegEmailOk = /\A#{RegEmailName}@#{RegDomainHead}#{RegDomainTLD}\z/i | |
def self.authenticate(email, secret) | |
u = User.first(:conditions => {:email => email.downcase}) | |
u && u.authenticated?(secret) ? u : nil | |
end | |
validates_length_of :email, :within => 6..100, :allow_blank => true | |
validates_format_of :email, :with => RegEmailOk, :allow_blank => true | |
PasswordRequired = Proc.new { |u| u.password_required? } | |
validates_presence_of :password, :if => PasswordRequired | |
validates_confirmation_of :password, :if => PasswordRequired, :allow_nil => true | |
validates_length_of :password, :minimum => 6, :if => PasswordRequired, :allow_nil => true | |
def authenticated?(secret) | |
password == secret ? true : false | |
end | |
def password | |
if crypted_password.present? | |
@password ||= BCrypt::Password.new(crypted_password) | |
else | |
nil | |
end | |
end | |
def password=(value) | |
if value.present? | |
@password = value | |
self.crypted_password = BCrypt::Password.create(value) | |
end | |
end | |
def email=(new_email) | |
new_email.downcase! unless new_email.nil? | |
write_attribute(:email, new_email) | |
end | |
def password_required? | |
crypted_password.blank? || !password.blank? | |
end | |
def set_password_code! | |
seed = "#{email}#{Time.now.to_s.split(//).sort_by {rand}.join}" | |
self.reset_password_code_until = 1.day.from_now | |
self.reset_password_code = Digest::SHA1.hexdigest(seed) | |
save! | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require 'test_helper' | |
class SessionsControllerTest < ActionController::TestCase | |
context "Visiting the sign in form" do | |
setup { get :new } | |
should_respond_with :success | |
should "display signup form" do | |
assert_select 'form[action=?]', session_path do | |
assert_select 'input[id=?]', 'email' | |
assert_select 'input[id=?]', 'password' | |
end | |
end | |
end | |
context "Submitting the sign in form with good credentials" do | |
setup do | |
@user = Factory(:user) | |
post :create, :email => @user.email, :password => 'testing' | |
end | |
should_be_signed_in_as { @user } | |
should_redirect_to_root | |
end | |
context "Submitting the sign in form with bad credentials" do | |
setup do | |
@user = Factory(:user) | |
post :create, :email => @user.email, :password => 'FAIL' | |
end | |
should_not_be_signed_in | |
should_redirect_to('sign in') { new_session_path } | |
end | |
context "Logging out" do | |
setup do | |
sign_in_as '[email protected]' | |
delete :destroy | |
end | |
should_not_be_signed_in | |
should_respond_with :success | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require 'test_helper' | |
class UserTest < ActiveSupport::TestCase | |
should "validate presence of email" do | |
user = User.new | |
user.should have_error_on(:email) | |
user.email = '[email protected]' | |
user.should_not have_error_on(:email) | |
end | |
should "validate length of email" do | |
user = User.new | |
user.email = 'a@a' | |
user.should have_error_on(:email) | |
user.email = '[email protected]' | |
end | |
should "always store email as lower case" do | |
user = User.new | |
user.email = '[email protected]' | |
user.email.should == '[email protected]' | |
end | |
should "be able to set user's reset password code" do | |
user = Factory(:user) | |
user.reset_password_code.should be_nil | |
user.reset_password_code_until.should be_nil | |
user.set_password_code! | |
user.reset_password_code.should_not be_nil | |
user.reset_password_code_until.should_not be_nil | |
end | |
context "Authentication" do | |
should 'work with existing email and correct password' do | |
user = Factory(:user) | |
User.authenticate(user.email, 'testing').should == user | |
end | |
should 'work with existing email (case insensitive) and password' do | |
user = Factory(:user) | |
User.authenticate(user.email.upcase, 'testing').should == user | |
end | |
should 'not work with existing email and incorrect password' do | |
User.authenticate('[email protected]', 'foobar').should be_nil | |
end | |
should 'not work with non-existant email' do | |
User.authenticate('[email protected]', 'foobar').should be_nil | |
end | |
end | |
context "password" do | |
should 'be required if crypted password is blank' do | |
User.new.should have_error_on(:password) | |
end | |
should 'not be required if crypted password is present' do | |
user = User.new | |
user.crypted_password = BCrypt::Password.create('foobar') | |
user.should_not have_error_on(:password) | |
end | |
should "validate the length of password" do | |
user = User.new | |
user.password = '1234' | |
user.should have_error_on(:password) | |
user.password = '123456' | |
user.should_not have_error_on(:password) | |
end | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require 'test_helper' | |
class UsersControllerTest < ActionController::TestCase | |
context "Visiting the signup form" do | |
setup { get :new } | |
should_respond_with :success | |
end | |
context "Attempting to signup with valid information" do | |
setup { post :create, :user => Factory.attributes_for(:user) } | |
should_change 'User.count', :by => 1 | |
should "sign user in" do | |
session[:user_id].should == assigns(:user).id | |
end | |
should_redirect_to('home page') { root_path } | |
end | |
context "Attempting to signup with invalid information" do | |
setup { post :create, :user => {}} | |
should_not_change 'User.count' | |
should_not_be_signed_in | |
should_render_template :new | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment