josevalim-auth_helpers 0.1.2 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGELOG +2 -7
- data/README +54 -72
- data/lib/auth_helpers.rb +8 -20
- data/lib/auth_helpers/model/associatable.rb +5 -0
- data/lib/auth_helpers/model/confirmable.rb +32 -42
- data/lib/auth_helpers/model/recoverable.rb +27 -38
- data/lib/auth_helpers/model/updatable.rb +39 -0
- data/lib/auth_helpers/notifier.rb +20 -19
- data/lib/auth_helpers/spec/confirmable.rb +24 -30
- data/lib/auth_helpers/spec/notifier.rb +10 -11
- data/lib/auth_helpers/spec/recoverable.rb +25 -32
- data/lib/auth_helpers/spec/updatable.rb +37 -0
- data/views/auth_helpers/notifier/create_confirmation.erb +1 -0
- data/views/auth_helpers/notifier/resend_confirmation.erb +1 -0
- data/views/auth_helpers/notifier/reset_password.erb +1 -1
- data/views/auth_helpers/notifier/update_confirmation.erb +1 -0
- metadata +10 -16
- data/lib/auth_helpers/migration.rb +0 -56
- data/lib/auth_helpers/model/authenticable.rb +0 -105
- data/lib/auth_helpers/model/rememberable.rb +0 -71
- data/lib/auth_helpers/model/validatable.rb +0 -43
- data/lib/auth_helpers/spec/associatable.rb +0 -34
- data/lib/auth_helpers/spec/authenticable.rb +0 -87
- data/lib/auth_helpers/spec/rememberable.rb +0 -83
- data/lib/auth_helpers/spec/validatable.rb +0 -29
- data/views/auth_helpers/notifier/confirmation_code.erb +0 -1
- data/views/auth_helpers/notifier/email_changed.erb +0 -1
- data/views/auth_helpers/notifier/new_account.erb +0 -1
@@ -1,105 +0,0 @@
|
|
1
|
-
require 'digest/sha1'
|
2
|
-
require File.join(File.dirname(__FILE__), '..', 'notifier')
|
3
|
-
|
4
|
-
module AuthHelpers
|
5
|
-
module Model
|
6
|
-
|
7
|
-
# Adds methods that helps you to authenticate an user. It requires that you set
|
8
|
-
# a constant called SALT in your model.
|
9
|
-
#
|
10
|
-
module Authenticable
|
11
|
-
def self.included(base)
|
12
|
-
base.send :attr_accessor, :email_confirmation, :password_confirmation
|
13
|
-
base.send :attr_accessible, :email, :email_confirmation, :password, :password_confirmation
|
14
|
-
base.extend ClassMethods
|
15
|
-
end
|
16
|
-
|
17
|
-
# Overwrite update attributes to deal with email, password and confirmations.
|
18
|
-
#
|
19
|
-
def update_attributes(options)
|
20
|
-
# Reject email if it didn't change or is blank
|
21
|
-
options.delete(:email) if options[:email].blank? || options[:email] == self.email
|
22
|
-
options.delete(:email_confirmation) if options[:email_confirmation].blank?
|
23
|
-
|
24
|
-
# Reject password if it didn't change or is blank
|
25
|
-
options.delete(:password) if options[:password].blank? || self.authenticate?(options[:password])
|
26
|
-
options.delete(:password_confirmation) if options[:password_confirmation].blank?
|
27
|
-
|
28
|
-
# Force confirmations (if confirmation is nil, it won't validate, it has to be at least blank)
|
29
|
-
options[:email_confirmation] ||= '' if options[:email]
|
30
|
-
options[:password_confirmation] ||= '' if options[:password]
|
31
|
-
|
32
|
-
if super(options)
|
33
|
-
# Generate a new confirmation code, save and send it.
|
34
|
-
if options[:email] && respond_to?(:set_confirmation_code)
|
35
|
-
self.set_confirmation_code
|
36
|
-
self.save(false)
|
37
|
-
|
38
|
-
AuthHelpers::Notifier.deliver_email_changed(self)
|
39
|
-
end
|
40
|
-
|
41
|
-
return true
|
42
|
-
end
|
43
|
-
|
44
|
-
return false
|
45
|
-
end
|
46
|
-
|
47
|
-
# Authenticate the account by encrypting the password sent and comparing with the hashed password.
|
48
|
-
#
|
49
|
-
def authenticate?(auth_password)
|
50
|
-
self.hashed_password.not_blank? && self.class.send(:encrypt, auth_password, self.salt) == self.hashed_password
|
51
|
-
end
|
52
|
-
|
53
|
-
# Get the password
|
54
|
-
#
|
55
|
-
def password
|
56
|
-
@password
|
57
|
-
end
|
58
|
-
|
59
|
-
# Sets the password for this account by creating a salt and encrypting the password sent.
|
60
|
-
#
|
61
|
-
def password=(new_password)
|
62
|
-
@password = new_password
|
63
|
-
|
64
|
-
self.salt = AuthHelpers.random_string(10)
|
65
|
-
self.hashed_password = self.class.send(:encrypt, @password, self.salt)
|
66
|
-
end
|
67
|
-
|
68
|
-
module ClassMethods
|
69
|
-
|
70
|
-
# Finds and authenticate an record, setting error messages in case the object
|
71
|
-
# can't be authenticated.
|
72
|
-
#
|
73
|
-
# Account.find_and_authenticate(:email => 'my@email.com', :password => '123456')
|
74
|
-
#
|
75
|
-
def find_and_authenticate(options={})
|
76
|
-
authenticable = AuthHelpers.find_or_initialize_by_unless_blank(self, :email, options[:email])
|
77
|
-
|
78
|
-
unless authenticable.authenticate?(options[:password])
|
79
|
-
if options[:email].blank?
|
80
|
-
authenticable.errors.add :email, :blank
|
81
|
-
elsif options[:password].blank?
|
82
|
-
authenticable.errors.add :password, :blank
|
83
|
-
elsif authenticable.new_record?
|
84
|
-
authenticable.errors.add :email, :not_found, :email => options[:email]
|
85
|
-
else
|
86
|
-
authenticable.errors.add :password, :invalid, :email => options[:email]
|
87
|
-
end
|
88
|
-
end
|
89
|
-
|
90
|
-
return authenticable
|
91
|
-
end
|
92
|
-
|
93
|
-
protected
|
94
|
-
|
95
|
-
# Encrypts a string using a fixed salt and a variable salt.
|
96
|
-
#
|
97
|
-
def encrypt(password, salt)
|
98
|
-
return nil if password.blank? || salt.blank?
|
99
|
-
Digest::SHA1.hexdigest(password + self::SALT + salt)
|
100
|
-
end
|
101
|
-
end
|
102
|
-
end
|
103
|
-
|
104
|
-
end
|
105
|
-
end
|
@@ -1,71 +0,0 @@
|
|
1
|
-
require File.join(File.dirname(__FILE__), '..', 'notifier')
|
2
|
-
|
3
|
-
module AuthHelpers
|
4
|
-
module Model
|
5
|
-
|
6
|
-
# Adds remember_me to the model. The token is valid for two weeks, you can
|
7
|
-
# can change this by overwriting the token_expiration_interval method.
|
8
|
-
#
|
9
|
-
module Rememberable
|
10
|
-
def self.included(base)
|
11
|
-
base.extend ClassMethods
|
12
|
-
base.class_eval do
|
13
|
-
attr_accessor :remember_me
|
14
|
-
attr_accessible :remember_me
|
15
|
-
alias :remember_me? :remember_me
|
16
|
-
end
|
17
|
-
end
|
18
|
-
|
19
|
-
# Call to set and save a remember me token
|
20
|
-
#
|
21
|
-
def remember_me!
|
22
|
-
self.token = AuthHelpers.generate_unique_string_for(self.class, :token, 40)
|
23
|
-
self.token_expires_at = token_expiration_interval
|
24
|
-
self.save(false)
|
25
|
-
end
|
26
|
-
|
27
|
-
# Call to forget and save the token
|
28
|
-
#
|
29
|
-
def forget_me!
|
30
|
-
self.token = nil
|
31
|
-
self.token_expires_at = nil
|
32
|
-
self.save(false)
|
33
|
-
end
|
34
|
-
|
35
|
-
# Returns a hash to be store in session
|
36
|
-
#
|
37
|
-
def remember_me_cookie_hash
|
38
|
-
{ :value => self.token, :expires => self.token_expires_at }
|
39
|
-
end
|
40
|
-
|
41
|
-
# Change if you want to set another token_expiration_interval or add
|
42
|
-
# custom logic (admin has one day token, clients have 2 weeks).
|
43
|
-
#
|
44
|
-
def token_expiration_interval
|
45
|
-
2.weeks.from_now
|
46
|
-
end
|
47
|
-
|
48
|
-
module ClassMethods
|
49
|
-
# Find the user with the given token only if it has not expired at.
|
50
|
-
#
|
51
|
-
def find_by_remember_me_token(token)
|
52
|
-
self.find(:first, :conditions => [ "token = ? AND token_expires_at > CURRENT_TIMESTAMP", token ])
|
53
|
-
end
|
54
|
-
|
55
|
-
# Overwrites find and authenticate to deal with the remember me key.
|
56
|
-
#
|
57
|
-
def find_and_authenticate(options={})
|
58
|
-
rememberable, remember_me = options.key?(:remember_me), options.delete(:remember_me)
|
59
|
-
authenticable = super(options)
|
60
|
-
|
61
|
-
if rememberable && authenticable.errors.empty?
|
62
|
-
remember_me == '1' ? authenticable.remember_me! : authenticable.forget_me!
|
63
|
-
end
|
64
|
-
|
65
|
-
authenticable
|
66
|
-
end
|
67
|
-
end
|
68
|
-
|
69
|
-
end
|
70
|
-
end
|
71
|
-
end
|
@@ -1,43 +0,0 @@
|
|
1
|
-
module AuthHelpers
|
2
|
-
module Model
|
3
|
-
|
4
|
-
# Include validations.
|
5
|
-
#
|
6
|
-
# If you want to scope the validate uniqueness of, you have to set a constant
|
7
|
-
# SCOPE in your class.
|
8
|
-
#
|
9
|
-
# class Account < ActiveRecord::Base
|
10
|
-
# SALT = 'my_project_salt'
|
11
|
-
# SCOPE = [ :company_id ]
|
12
|
-
#
|
13
|
-
# include AuthHelpers::Models::Authenticable
|
14
|
-
# include AuthHelpers::Models::Validatable
|
15
|
-
# end
|
16
|
-
#
|
17
|
-
# Another hook provided is the password_required? method. It always returns
|
18
|
-
# true, but you can overwrite it to add custom logic.
|
19
|
-
#
|
20
|
-
module Validatable
|
21
|
-
EMAIL_REGEXP = /^([^@"'><&\s\,\;]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i
|
22
|
-
|
23
|
-
def self.included(base)
|
24
|
-
base.class_eval do
|
25
|
-
validates_presence_of :email
|
26
|
-
validates_length_of :email, :maximum => 100, :allow_blank => true
|
27
|
-
validates_format_of :email, :with => EMAIL_REGEXP, :allow_blank => true
|
28
|
-
validates_uniqueness_of :email, :case_sensitive => false, :allow_blank => true,
|
29
|
-
:scope => (defined?(base::SCOPE) ? base::SCOPE : [])
|
30
|
-
validates_confirmation_of :email
|
31
|
-
|
32
|
-
validates_presence_of :password, :if => :password_required?
|
33
|
-
validates_length_of :password, :within => 6..20, :allow_blank => true
|
34
|
-
validates_confirmation_of :password
|
35
|
-
|
36
|
-
# Overwrite if password is not required or implement custom logic
|
37
|
-
def password_required?; true; end
|
38
|
-
end
|
39
|
-
end
|
40
|
-
end
|
41
|
-
|
42
|
-
end
|
43
|
-
end
|
@@ -1,34 +0,0 @@
|
|
1
|
-
module AuthHelpers
|
2
|
-
module Spec
|
3
|
-
|
4
|
-
module Associatable
|
5
|
-
def self.included(base)
|
6
|
-
klass = base.described_class
|
7
|
-
|
8
|
-
column = klass.columns.detect{|c| c.name =~ /_id$/ }
|
9
|
-
raise ScriptError, "Could not find a column that ends with id in #{base.name.tableize}" unless column
|
10
|
-
|
11
|
-
association = column.name.gsub(/_id$/, '').to_sym
|
12
|
-
polymorphic = !!klass.columns.detect{ |c| c.name == "#{association}_type" }
|
13
|
-
|
14
|
-
base.class_eval do
|
15
|
-
should_belong_to association, :validate => true, :dependent => :destroy,
|
16
|
-
:autosave => true, :polymorphic => polymorphic
|
17
|
-
|
18
|
-
it "should validate associated #{association}" do
|
19
|
-
associatable = base.described_class.create(@valid_attributes.merge(:"#{association}_attributes" => {}))
|
20
|
-
associatable.should_not be_valid
|
21
|
-
|
22
|
-
unless associatable.send(association).errors.empty?
|
23
|
-
associatable.errors.should be_empty # this should be blank since errors is
|
24
|
-
# on the associated object.
|
25
|
-
|
26
|
-
associatable.send(association).errors.should_not be_empty
|
27
|
-
end
|
28
|
-
end
|
29
|
-
end
|
30
|
-
end
|
31
|
-
end
|
32
|
-
|
33
|
-
end
|
34
|
-
end
|
@@ -1,87 +0,0 @@
|
|
1
|
-
module AuthHelpers
|
2
|
-
module Spec
|
3
|
-
|
4
|
-
module Authenticable
|
5
|
-
def self.included(base)
|
6
|
-
base.class_eval do
|
7
|
-
describe 'on authentication' do
|
8
|
-
it { should_not allow_mass_assignment_of(:salt, :hashed_password) }
|
9
|
-
|
10
|
-
it "should set a salt and hashed_password when assigning password" do
|
11
|
-
salt_value = '0123456789'
|
12
|
-
password_value = 'abcdef'
|
13
|
-
hashed_password_value = 'nice' * 10
|
14
|
-
|
15
|
-
AuthHelpers.should_receive(:random_string).with(10).and_return(salt_value)
|
16
|
-
base.described_class.should_receive(:encrypt).with(password_value, salt_value).and_return(hashed_password_value)
|
17
|
-
|
18
|
-
authenticable = base.described_class.new
|
19
|
-
authenticable.password = password_value
|
20
|
-
|
21
|
-
authenticable.salt.should == salt_value
|
22
|
-
authenticable.hashed_password.should == hashed_password_value
|
23
|
-
end
|
24
|
-
|
25
|
-
it "should authenticate users with valid password" do
|
26
|
-
authenticable = base.described_class.new
|
27
|
-
authenticable.authenticate?('abcdef').should be_false
|
28
|
-
|
29
|
-
authenticable.password = 'abcdef'
|
30
|
-
authenticable.authenticate?(nil).should be_false
|
31
|
-
authenticable.authenticate?('notvalid').should be_false
|
32
|
-
authenticable.authenticate?('abcdef').should be_true
|
33
|
-
end
|
34
|
-
|
35
|
-
it "should find and authenticate an account by email" do
|
36
|
-
base.described_class.create!(@valid_attributes)
|
37
|
-
|
38
|
-
authenticable = base.described_class.find_and_authenticate(:email => @valid_attributes[:email], :password => @valid_attributes[:password])
|
39
|
-
authenticable.errors.should be_empty
|
40
|
-
|
41
|
-
authenticable = base.described_class.find_and_authenticate(:email => @valid_attributes[:email], :password => @valid_attributes[:password].to_s.reverse)
|
42
|
-
authenticable.errors.on(:password).should == authenticable.errors.generate_message(:password, :invalid, @valid_attributes)
|
43
|
-
|
44
|
-
authenticable = base.described_class.find_and_authenticate(:email => @valid_attributes[:email], :password => '')
|
45
|
-
authenticable.errors.on(:password).should == authenticable.errors.generate_message(:password, :blank)
|
46
|
-
|
47
|
-
authenticable = base.described_class.find_and_authenticate(:email => 'does.not.exist@email.com', :password => @valid_attributes[:password].to_s.reverse)
|
48
|
-
authenticable.new_record?.should be_true
|
49
|
-
authenticable.errors.on(:email).should == authenticable.errors.generate_message(:email, :not_found)
|
50
|
-
|
51
|
-
authenticable = base.described_class.find_and_authenticate(:email => '', :password => 'notvalid')
|
52
|
-
authenticable.new_record?.should be_true
|
53
|
-
authenticable.errors.on(:email).should == authenticable.errors.generate_message(:email, :blank)
|
54
|
-
end
|
55
|
-
|
56
|
-
describe "on update" do
|
57
|
-
before(:each) do
|
58
|
-
@authenticable = base.described_class.create!(@valid_attributes)
|
59
|
-
ActionMailer::Base.deliveries = []
|
60
|
-
end
|
61
|
-
|
62
|
-
it "should ignore e-mail confirmation if e-mail has not changed" do
|
63
|
-
attributes = { :email => @authenticable.email, :email_confirmation => '' }
|
64
|
-
@authenticable.update_attributes(attributes).should be_true
|
65
|
-
end
|
66
|
-
|
67
|
-
it "should ignore password confirmation if password has not changed" do
|
68
|
-
attributes = { :password => @valid_attributes[:password], :password_confirmation => '' }
|
69
|
-
@authenticable.update_attributes(attributes).should be_true
|
70
|
-
end
|
71
|
-
|
72
|
-
if base.described_class.new.respond_to?(:set_confirmation_code, true)
|
73
|
-
it "should send an e-mail if e-mail changes" do
|
74
|
-
attributes = { :email => @valid_attributes[:email].to_s.next, :email_confirmation => @valid_attributes[:email].to_s.next }
|
75
|
-
@authenticable.update_attributes(attributes).should be_true
|
76
|
-
@authenticable.email.should == @valid_attributes[:email].to_s.next
|
77
|
-
ActionMailer::Base.deliveries.length.should == 1
|
78
|
-
end
|
79
|
-
end
|
80
|
-
end
|
81
|
-
end
|
82
|
-
end
|
83
|
-
end
|
84
|
-
end
|
85
|
-
|
86
|
-
end
|
87
|
-
end
|
@@ -1,83 +0,0 @@
|
|
1
|
-
module AuthHelpers
|
2
|
-
module Spec
|
3
|
-
|
4
|
-
module Rememberable
|
5
|
-
def self.included(base)
|
6
|
-
base.class_eval do
|
7
|
-
describe 'when authenticating' do
|
8
|
-
before(:each){ @rememberable = base.described_class.create!(@valid_attributes) }
|
9
|
-
|
10
|
-
it 'should set the remember me token' do
|
11
|
-
@rememberable.remember_me!
|
12
|
-
@rememberable.token.should_not be_nil
|
13
|
-
end
|
14
|
-
|
15
|
-
it 'should set the remember me token creation date' do
|
16
|
-
@rememberable.remember_me!
|
17
|
-
@rememberable.token_expires_at.should_not be_nil
|
18
|
-
end
|
19
|
-
|
20
|
-
it 'should return a remember_me cookie hash' do
|
21
|
-
@rememberable.remember_me!
|
22
|
-
@rememberable.remember_me_cookie_hash[:value].should == @rememberable.token
|
23
|
-
@rememberable.remember_me_cookie_hash[:expires].should == @rememberable.token_expires_at
|
24
|
-
end
|
25
|
-
|
26
|
-
it 'should forget the remember me token' do
|
27
|
-
@rememberable.remember_me!
|
28
|
-
@rememberable.forget_me!
|
29
|
-
@rememberable.token.should be_nil
|
30
|
-
end
|
31
|
-
|
32
|
-
it 'should forget the remember me token creation date' do
|
33
|
-
@rememberable.remember_me!
|
34
|
-
@rememberable.forget_me!
|
35
|
-
@rememberable.token_expires_at.should be_nil
|
36
|
-
end
|
37
|
-
|
38
|
-
if base.described_class.ancestors.include?(::AuthHelpers::Model::Authenticable)
|
39
|
-
it 'should find, authenticate and set token if remember me is true' do
|
40
|
-
base.described_class.find_and_authenticate(:email => @valid_attributes[:email], :password => @valid_attributes[:password], :remember_me => "1")
|
41
|
-
@rememberable.reload
|
42
|
-
@rememberable.token.should_not be_nil
|
43
|
-
end
|
44
|
-
|
45
|
-
it 'should find, authenticate and clear token if remember me is false' do
|
46
|
-
@rememberable.remember_me!
|
47
|
-
base.described_class.find_and_authenticate(:email => @valid_attributes[:email], :password => @valid_attributes[:password], :remember_me => "0")
|
48
|
-
@rememberable.reload
|
49
|
-
@rememberable.token.should be_nil
|
50
|
-
end
|
51
|
-
|
52
|
-
it 'should not set or clear token if remember me is not set' do
|
53
|
-
@rememberable.remember_me!
|
54
|
-
base.described_class.find_and_authenticate(:email => @valid_attributes[:email], :password => @valid_attributes[:password])
|
55
|
-
@rememberable.reload
|
56
|
-
@rememberable.token.should_not be_nil
|
57
|
-
end
|
58
|
-
|
59
|
-
it 'should not set or clear token if user cannot authenticate' do
|
60
|
-
base.described_class.find_and_authenticate(:email => @valid_attributes[:email], :password => @valid_attributes[:password].to_s.next, :remember_me => "1")
|
61
|
-
@rememberable.reload
|
62
|
-
@rememberable.token.should be_nil
|
63
|
-
end
|
64
|
-
end
|
65
|
-
|
66
|
-
it 'should be found by remember me token' do
|
67
|
-
@rememberable.remember_me!
|
68
|
-
base.described_class.find_by_remember_me_token(@rememberable.token).should_not be_nil
|
69
|
-
base.described_class.find_by_remember_me_token(@rememberable.token.next).should be_nil
|
70
|
-
end
|
71
|
-
|
72
|
-
it 'should not be found if remember me token is expired' do
|
73
|
-
@rememberable.remember_me!
|
74
|
-
@rememberable.update_attribute(:token_expires_at, 1.day.ago)
|
75
|
-
base.described_class.find_by_remember_me_token(@rememberable.token).should be_nil
|
76
|
-
end
|
77
|
-
end
|
78
|
-
end
|
79
|
-
end
|
80
|
-
end
|
81
|
-
|
82
|
-
end
|
83
|
-
end
|
@@ -1,29 +0,0 @@
|
|
1
|
-
module AuthHelpers
|
2
|
-
module Spec
|
3
|
-
|
4
|
-
module Validatable
|
5
|
-
def self.included(base)
|
6
|
-
base.class_eval do
|
7
|
-
describe 'validation' do
|
8
|
-
should_validate_presence_of :email
|
9
|
-
should_validate_length_of :email, :within => 0..100, :allow_blank => true
|
10
|
-
should_validate_confirmation_of :email
|
11
|
-
|
12
|
-
it {
|
13
|
-
base.described_class.create!(@valid_attributes)
|
14
|
-
should validate_uniqueness_of(:email, :case_sensitive => false, :allow_blank => true,
|
15
|
-
:scope => (defined?(base.described_class::SCOPE) ? base.described_class::SCOPE : []))
|
16
|
-
}
|
17
|
-
|
18
|
-
should_not_allow_values_for :email, 'josevalim', 'a@a@a.com', 'jose@com'
|
19
|
-
|
20
|
-
should_validate_presence_of :password
|
21
|
-
should_validate_length_of :password, :within => 6..20, :allow_blank => true
|
22
|
-
should_validate_confirmation_of :password
|
23
|
-
end
|
24
|
-
end
|
25
|
-
end
|
26
|
-
end
|
27
|
-
|
28
|
-
end
|
29
|
-
end
|