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 CHANGED
@@ -1,17 +1,12 @@
1
- # Version 0.1
1
+ # Version 0.2
2
2
 
3
3
  * First version with the following modules:
4
4
 
5
5
  AuthHelpers::Model::Associatable
6
- AuthHelpers::Model::Authenticable
7
6
  AuthHelpers::Model::Confirmable
8
7
  AuthHelpers::Model::Recoverable
9
- AuthHelpers::Model::Rememberable
10
- AuthHelpers::Model::Validatable
8
+ AuthHelpers::Model::Updatable
11
9
 
12
10
  Plus:
13
11
 
14
12
  AuthHelpers::Notifier
15
- AuthHelpers::Migration
16
-
17
- The first to deal with Notifications and the second with migrations.
data/README CHANGED
@@ -9,20 +9,9 @@ You can also read this README in pretty html at the GitHub project Wiki page:
9
9
  Description
10
10
  -----------
11
11
 
12
- AuthHelpers is a collection of modules to include in your model to deal with
13
- authentication.
14
-
15
- Why? Authentication is something that you need to do right since the beginning,
16
- otherwise it will haunt you until the end of the project.
17
-
18
- Gladly, Rails community has two awesome gems for this: Clearance and AuthLogic.
19
- While the first gives you a simple but full, ready-to-go engine, the second
20
- is a complex, fully featured authencation library.
21
-
22
- While working in different projects, requisites change and sometimes you need
23
- something that works between something simple and something fully featured. This
24
- is the scope of AuthHelpers: you have modules and you include them where you
25
- want.
12
+ AuthHelpers is a collection of modules to improve your Authlogic models. It
13
+ mainly exposes some convenience methods to help on confirmation and passwords
14
+ management, it also deals automatically with associations and notifications.
26
15
 
27
16
  Installation
28
17
  ------------
@@ -41,14 +30,15 @@ Modules
41
30
  -------
42
31
 
43
32
  class Account < ActiveRecord::Base
44
- SALT = APP_NAME
33
+ acts_as_authentic do |a|
34
+ a.validations_scope = :accountable_type
35
+ a.require_password_confirmation = false
36
+ end
45
37
 
46
38
  include AuthHelpers::Model::Associatable
47
- include AuthHelpers::Model::Authenticable
48
39
  include AuthHelpers::Model::Confirmable
49
40
  include AuthHelpers::Model::Recoverable
50
- include AuthHelpers::Model::Rememberable
51
- include AuthHelpers::Model::Validatable
41
+ include AuthHelpers::Model::Updatable
52
42
  end
53
43
 
54
44
  == Associatable
@@ -58,53 +48,45 @@ in the table. This module exists because, whenever it's possible, I follow the
58
48
  pattern of having an account model and then all accountable objects uses this
59
49
  model (it autodetects polymorphic associations too).
60
50
 
61
- == Authenticable
62
-
63
- It adds password, salt and encrypt behavior. Adds find_and_authenticate class
64
- method and authenticate?(password) as instance method. It requires a constant
65
- named SALT set in your model.
66
-
67
51
  == Confirmable
68
52
 
69
- Adds the confirmation_code handling. It sends an e-mail to the user on account
70
- creation, and adds find_and_confirm, find_and_resend_confirmation_code as class
71
- methods and confirmed? and confirm as instance methods.
53
+ Adds the confirmation handling. It sends an e-mail to the user on account
54
+ creation, and adds find_and_confirm, find_and_resend_confirmation_instructions
55
+ as class methods plus confirmed? and confirm! as instance methods.
72
56
 
73
- When used with Authenticable, also sends an e-mail with a new confirmation code
74
- whenever the user changes his e-mail address.
57
+ When used with Updatable, also sends an e-mail the user changes his e-mail address.
75
58
 
76
59
  == Recoverable
77
60
 
78
- Adds the reset_password_code handling. Adds find_and_resend_confirmation_code
79
- and find_and_reset_password class methods.
80
-
81
- == Rememberable
61
+ Adds the reset password code handling. Adds find_and_resend_confirmation_instructions
62
+ and find_and_reset_password class methods plus reset_password instance method.
82
63
 
83
- Manages a token to be stored in cookies. Adds find_by_remember_me_token (which
84
- only returns a record if the token hasn't expired yet), remember_me! and forget_me!
85
- methods.
64
+ == Updatable
86
65
 
87
- Whenever used with Authentication, it handles the remember_me method inside
88
- find_and_authenticate.
66
+ It adds email and password confirmations and hack into update_attributes to
67
+ ensure the another confirmation instruction is sent when the user changes the
68
+ e-mail.
89
69
 
90
- == Validatable
70
+ Authlogic already supports password confirmation, but it's coupled with the
71
+ validates_length_of :password_confirmation, which is unecessary. So in order
72
+ to use this module, is advisable to disable require_password_confirmation from
73
+ Authlogic.
91
74
 
92
- Add validations to your e-mail and password. If you have a constant in your model
93
- named SCOPE, it will add this to validate_uniqueness_of.
75
+ acts_as_authentic do |a|
76
+ a.require_password_confirmation = false
77
+ end
94
78
 
95
79
  Specs
96
80
  -----
97
81
 
98
- All those modules comes with specs, that's why the library has not tests per se.
82
+ All modules come with specs, that's why the library does not have tests per se.
99
83
  So if you want to test the Account model declared above, just do:
100
84
 
101
85
  describe Account do
102
86
  include AuthHelpers::Spec::Associatable
103
- include AuthHelpers::Spec::Authenticable
104
87
  include AuthHelpers::Spec::Confirmable
105
88
  include AuthHelpers::Spec::Recoverable
106
- include AuthHelpers::Spec::Rememberable
107
- include AuthHelpers::Spec::Validatable
89
+ include AuthHelpers::Spec::Updatable
108
90
 
109
91
  before(:each) do
110
92
  @valid_attributes = {
@@ -120,28 +102,8 @@ So if you want to test the Account model declared above, just do:
120
102
  end
121
103
  end
122
104
 
123
- The only requisite you have for the tests is to have a @valid_attributes instance
124
- variable set with a hash of valid attributes. You also need Remarkable to run
125
- those tests.
126
-
127
- Migrations
128
- ----------
129
-
130
- While AuthHelpers gives you the flexibity to choose which model you want to add
131
- your validations, it takes from you the freedom to choose what are the column
132
- names. However it makes easier to create your migrations. This is a migration up
133
- example for the Account model above:
134
-
135
- create_table :accounts do |t|
136
- t.references :accountable, :polymorphic => true
137
- t.extend AuthHelpers::Migration
138
-
139
- t.authenticable
140
- t.confirmable
141
- t.recoverable
142
- t.rememberable
143
- t.timestamps
144
- end
105
+ The only requirment you have for the tests is to have a @valid_attributes
106
+ instance variable set with a hash of valid attributes.
145
107
 
146
108
  Notifications
147
109
  -------------
@@ -155,12 +117,32 @@ want to prettify your notification views, so you just need to do:
155
117
  Then make a copy of the plugin views folder to your app/views and start to work
156
118
  on them.
157
119
 
158
- Need more?
159
- ----------
120
+ Notification has some basic specs which fail if you don't include the perishable
121
+ token in your emails. Just put a file in spec/models/auth_helpers/notifier.rb
122
+ with the following contents:
123
+
124
+ require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
125
+
126
+ module AuthHelpers
127
+ describe Notifier do
128
+ include AuthHelpers::Spec::Notifier
129
+ end end
130
+
131
+ I18n
132
+ ----
133
+
134
+ Those helpers rely on I18n. Assuming the account model below, you need to
135
+ configure the following messages:
160
136
 
161
- I'm open to extend this library to include more modules. So if you want an
162
- Invitable module, fork the project, add it and then send it to me. I will pull
163
- it in gladly.
137
+ activerecord:
138
+ errors:
139
+ models:
140
+ account:
141
+ already_confirmed: was already confirmed
142
+ not_found: could not be found. Are you sure you gave the right e-mail address?
143
+ perishable_token:
144
+ invalid_confirmation: is invalid. Are you sure you copied the right link from your e-mail?
145
+ invalid_reset_password: is invalid. Are you sure you copied the right link from your e-mail?
164
146
 
165
147
  Example app
166
148
  -----------
data/lib/auth_helpers.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  module AuthHelpers
2
- # Helper that find or initialize an object by attribute only if the given value is not blank.
3
- # If it's blank, create a new object using :new.
2
+ # Helper that find or initialize an object by attribute only if the given value
3
+ # is not blank. If it's blank, create a new object using :new.
4
4
  #
5
5
  def self.find_or_initialize_by_unless_blank(klass, attr, value)
6
6
  if value.blank?
@@ -10,25 +10,13 @@ module AuthHelpers
10
10
  end
11
11
  end
12
12
 
13
- # Helpers that generates a unique code for the given attribute by checking in
14
- # the database if the code already exists.
13
+ # Creates a new record, assigning the perishable token and an error message.
15
14
  #
16
- def self.generate_unique_string_for(klass, attr, length=40)
17
- begin
18
- value = AuthHelpers.random_string(length)
19
- end while klass.send(:"find_by_#{attr}", value)
20
-
21
- value
15
+ def self.new_with_perishable_token_error(klass, message=:invalid, options={})
16
+ record = klass.new(options)
17
+ record.perishable_token = options[:perishable_token]
18
+ record.errors.add(:perishable_token, message, :default => :invalid)
19
+ record
22
20
  end
23
21
 
24
- # Create a random string with the given length using letters and numbers.
25
- #
26
- def self.random_string(length)
27
- chars = ('a'..'z').to_a + ('A'..'Z').to_a + ('0'..'9').to_a
28
-
29
- newpass = ''
30
- 1.upto(length) { |i| newpass << chars.rand }
31
-
32
- return newpass
33
- end
34
22
  end
@@ -11,6 +11,11 @@ module AuthHelpers
11
11
  # Finally, if the *_id in the table has also *_type. It considers a polymorphic
12
12
  # association.
13
13
  #
14
+ # Whenever using this method with polymorphic association, don't forget to
15
+ # set the validation scope in AuthLogic.
16
+ #
17
+ # a.validations_scope = :accountable_type
18
+ #
14
19
  module Associatable
15
20
  def self.included(base)
16
21
  column = base.columns.detect{|c| c.name =~ /_id$/ }
@@ -8,76 +8,66 @@ module AuthHelpers
8
8
  module Confirmable
9
9
  def self.included(base)
10
10
  base.extend ClassMethods
11
- base.send :before_create, :set_confirmation_code
12
- base.send :after_create, :send_new_account_notification
11
+ base.send :after_create, :send_confirmation_instructions
13
12
  end
14
13
 
15
- # Returns true if is not a new record and the confirmation code is blank.
14
+ # Returns true if is not a new record and confirmed_at is not blank.
16
15
  #
17
16
  def confirmed?
18
- !self.new_record? && self.confirmation_code.blank?
17
+ !(self.new_record? || self.confirmed_at.nil?)
19
18
  end
20
19
 
21
- # Confirms an account by setting :confirmation_code to nil and setting the
22
- # confirmed_at field.
20
+ # Confirms the record by setting the confirmed at.
23
21
  #
24
22
  def confirm!
25
- self.confirmation_code = nil
26
- self.confirmed_at = Time.now.utc
27
- return self.save(false)
23
+ update_attribute(:confirmed_at, Time.now.utc)
28
24
  end
29
25
 
30
- protected
31
-
32
- # Generates a confirmation_code and sets confirmation_sent_at.
33
- # Does not save the object because it's used as a filter.
34
- #
35
- def set_confirmation_code
36
- self.confirmation_code = AuthHelpers.generate_unique_string_for(self.class, :confirmation_code, 40)
37
- self.confirmation_sent_at = Time.now.utc
38
- return true
39
- end
40
-
41
- # Send a notification to new account. Used as filter.
42
- #
43
- def send_new_account_notification
44
- AuthHelpers::Notifier.deliver_new_account(self)
45
- end
26
+ # Send confirmation isntructions in different scenarios. It resets the
27
+ # perishable token, confirmed_at date and set the confirmation_sent_at
28
+ # datetime.
29
+ #
30
+ def send_confirmation_instructions(on=:create)
31
+ self.reset_perishable_token
32
+ self.confirmed_at = nil
33
+ self.confirmation_sent_at = Time.now.utc
34
+ self.save(false)
35
+ AuthHelpers::Notifier.send(:"deliver_#{on}_confirmation", self)
36
+ end
46
37
 
47
38
  module ClassMethods
48
39
 
49
- # Receives a confirmation code, find the respective account and tries to set its confirmation code to nil.
50
- # If something goes wrong, return an account object with errors.
40
+ # Receives the perishable token and try to find a record to confirm the
41
+ # account. If it can't find the record, returns a new record with an
42
+ # error set on the perishable token.
51
43
  #
52
- def find_and_confirm(sent_confirmation_code)
53
- confirmable = AuthHelpers.find_or_initialize_by_unless_blank(self, :confirmation_code, sent_confirmation_code)
54
-
55
- if confirmable.new_record?
56
- confirmable.errors.add :confirmation_code, :invalid
57
- else
44
+ def find_and_confirm(options={})
45
+ if confirmable = self.find_using_perishable_token(options[:perishable_token])
58
46
  confirmable.confirm!
47
+ confirmable
48
+ else
49
+ AuthHelpers.new_with_perishable_token_error(self, :invalid_confirmation, options)
59
50
  end
60
-
61
- return confirmable
62
51
  end
63
52
 
64
- # Receives a hash with email and tries to find the account to resend the confirmation code.
65
- # If the e-mail can't be found or the account is already confirmed, return an account object
66
- # with errors.
53
+ # Receives a hash with email and tries to find a record to resend the
54
+ # confirmation instructions. If the record can't be found or it's already
55
+ # confirmed, set the appropriate error messages and return the object.
67
56
  #
68
- def find_and_resend_confirmation_code(options = {})
57
+ def find_and_resend_confirmation_instructions(options = {})
69
58
  confirmable = AuthHelpers.find_or_initialize_by_unless_blank(self, :email, options[:email])
70
59
 
71
60
  if confirmable.new_record?
72
- confirmable.errors.add :email, :not_found
61
+ confirmable.errors.add(:email, :not_found)
73
62
  elsif confirmable.confirmed?
74
- confirmable.errors.add :email, :already_confirmed
63
+ confirmable.errors.add(:email, :already_confirmed)
75
64
  else
76
- AuthHelpers::Notifier.deliver_confirmation_code(confirmable)
65
+ confirmable.send_confirmation_instructions(:resend)
77
66
  end
78
67
 
79
68
  return confirmable
80
69
  end
70
+
81
71
  end
82
72
 
83
73
  end
@@ -3,72 +3,61 @@ require File.join(File.dirname(__FILE__), '..', 'notifier')
3
3
  module AuthHelpers
4
4
  module Model
5
5
 
6
- # Adds a module that deals with forgot your password.
6
+ # Adds a module that deals with forgot your password. It overwrites the
7
+ # reset password method from authlogic for one that accepts a password.
7
8
  #
8
9
  module Recoverable
9
10
  def self.included(base)
10
- base.send(:attr_accessible, :reset_password_code)
11
11
  base.extend ClassMethods
12
12
  end
13
13
 
14
+ def reset_password(new_password, new_password_confirmation)
15
+ self.password = new_password || ""
16
+ self.password_confirmation = new_password_confirmation || "" if self.respond_to?(:password_confirmation)
17
+ end
18
+
14
19
  # Reset the password with the new_password is equals its confirmation and
15
20
  # set reset password code to nil.
16
21
  #
17
22
  def reset_password!(new_password, new_password_confirmation)
18
- self.password = new_password
19
- self.password_confirmation = new_password_confirmation
20
-
21
- if self.valid?
22
- self.reset_password_code = nil
23
- return self.save
24
- end
25
-
26
- false
27
- end
28
-
29
- # Set a reset password code in the database and send it through e-mail
30
- #
31
- def send_reset_password_code
32
- new_code = AuthHelpers.generate_unique_string_for(self.class, :reset_password_code, 40)
33
- self.update_attribute(:reset_password_code, new_code)
34
-
35
- AuthHelpers::Notifier.deliver_reset_password(self)
36
- return true
23
+ reset_password(new_password, new_password_confirmation)
24
+ self.save
37
25
  end
38
26
 
39
27
  module ClassMethods
40
28
 
41
- # Receives a hash with reset_password_code, password and password confirmation.
42
- # Tries to find the account with the sent password code, and then, if password and password
43
- # confirmation matches, changes the password. Otherwise return an account object with errors.
29
+ # Receives a hash with email and tries to find a record to resend reset
30
+ # password instructions. If the record can't be found, it sets the
31
+ # appropriate error messages and return the object.
44
32
  #
45
- def find_and_reset_password(options={})
46
- recoverable = AuthHelpers.find_or_initialize_by_unless_blank(self, :reset_password_code, options[:reset_password_code])
33
+ def find_and_send_reset_password_instructions(options={})
34
+ recoverable = AuthHelpers.find_or_initialize_by_unless_blank(self, :email, options[:email])
47
35
 
48
36
  if recoverable.new_record?
49
- recoverable.errors.add :reset_password_code, :invalid
37
+ recoverable.errors.add(:email, :not_found, options)
50
38
  else
51
- recoverable.reset_password!(options[:password], options[:password_confirmation])
39
+ recoverable.reset_perishable_token!
40
+ AuthHelpers::Notifier.deliver_reset_password(recoverable)
52
41
  end
53
42
 
54
43
  return recoverable
55
44
  end
56
45
 
57
- # Receives a hash with email and tries to find the account to send a new reset password code.
58
- # If the e-mail can't be found return an account object with errors.
46
+ # Receives a hash with perishable_token, password and password confirmation.
47
+ # If the password cannot be reset (confirmation fails, for example), it
48
+ # returns an object with errors.
59
49
  #
60
- def find_and_send_reset_password_code(options={})
61
- recoverable = AuthHelpers.find_or_initialize_by_unless_blank(self, :email, options[:email])
62
-
63
- if recoverable.new_record?
64
- recoverable.errors.add :email, :not_found, options
50
+ def find_and_reset_password(options={})
51
+ if recoverable = self.find_using_perishable_token(options[:perishable_token])
52
+ recoverable.reset_password!(options[:password], options[:password_confirmation])
53
+ recoverable
65
54
  else
66
- recoverable.send_reset_password_code
55
+ AuthHelpers.new_with_perishable_token_error(self, :invalid_reset_password, options)
67
56
  end
68
-
69
- return recoverable
70
57
  end
58
+
71
59
  end
60
+
72
61
  end
73
62
  end
74
63
  end