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.
@@ -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