Created

Embed URL

HTTPS clone URL

SSH clone URL

You can clone with HTTPS or SSH.

Download Gist

a starting point for authentication with mongomapper and rails

View authentication.rb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132
# 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
View authentication.rb
1 2 3 4 5 6
# factory girl factories
Factory.define(:user) do |u|
u.sequence(:email) { |n| "john#{n}@doe.com" }
u.password "testing"
u.password_confirmation "testing"
end
View authentication.rb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
<%- 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 -%>
View authentication.rb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
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
View authentication.rb
1 2 3 4 5 6 7 8 9 10 11
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
View authentication.rb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
# 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
View authentication.rb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
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 'john@doe.com'
delete :destroy
end
should_not_be_signed_in
should_respond_with :success
end
end
View authentication.rb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72
require 'test_helper'
 
class UserTest < ActiveSupport::TestCase
should "validate presence of email" do
user = User.new
user.should have_error_on(:email)
user.email = 'foo@bar.com'
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 = 'r@a.wk'
end
should "always store email as lower case" do
user = User.new
user.email = 'F@FOOBAR.COM'
user.email.should == 'f@foobar.com'
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('john@doe.com', 'foobar').should be_nil
end
should 'not work with non-existant email' do
User.authenticate('foo@bar.com', '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
View authentication.rb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
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
Something went wrong with that request. Please try again.