revise 0.0.1

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.
@@ -0,0 +1,107 @@
1
+ require 'bcrypt'
2
+
3
+ module Revise
4
+ module Models
5
+ module DatabaseAuthenticatable
6
+ extend ActiveSupport::Concern
7
+
8
+ MAILERS = []
9
+ HELPERS = ['Authentication']
10
+ CONTROLLERS = ['Sessions', 'Accounts']
11
+
12
+ included do
13
+ attr_reader :password, :current_password
14
+ attr_accessor :password_confirmation
15
+ end
16
+
17
+ def valid_for_authentication?
18
+ if super && valid_password?
19
+ true
20
+ else
21
+ false
22
+ end
23
+ end
24
+
25
+ def self.required_fields(klass)
26
+ [:encrypted_password] + klass.authentication_keys
27
+ end
28
+
29
+ def password=(new_password)
30
+ @password = new_password
31
+ self.encrypted_password = password_digest(@password) if @password.present?
32
+ end
33
+
34
+ def valid_password?(password=nil)
35
+ password = @password if password == nil
36
+
37
+ return false if encrypted_password.blank?
38
+
39
+ bcrypt = ::BCrypt::Password.new(encrypted_password)
40
+ password = ::BCrypt::Engine.hash_secret("#{password}#{self.class.pepper}", bcrypt.salt)
41
+
42
+ String.secure_compare(password, encrypted_password)
43
+ end
44
+
45
+ def clean_up_passwords
46
+ self.password = self.password_confirmation = nil
47
+ end
48
+
49
+ def update_with_password(params, *options)
50
+ current_password = params.delete(:current_password)
51
+
52
+ if params[:password].blank?
53
+ params.delete(:password)
54
+ params.delete(:password_confirmation) if params[:password_confirmation].blank?
55
+ end
56
+
57
+ result = if valid_password?(current_password)
58
+ update_attributes(params, *options)
59
+ else
60
+ self.assign_attributes(params, *options)
61
+ self.valid?
62
+ self.errors.add(:current_password, current_password.blank? ? :blank : :invalid)
63
+ false
64
+ end
65
+
66
+ clean_up_passwords
67
+ result
68
+ end
69
+
70
+ def update_without_password(params, *options)
71
+ params.delete(:password)
72
+ params.delete(:password_confirmation)
73
+
74
+ result = update_attributes(params, *options)
75
+ clean_up_passwords
76
+ result
77
+ end
78
+
79
+ def after_database_authentication
80
+ end
81
+
82
+ def authenticatable_salt
83
+ encrypted_password[0,29] if encrypted_password
84
+ end
85
+
86
+ protected
87
+
88
+ def password_digest(password)
89
+ ::BCrypt::Password.create("#{password}#{self.class.pepper}", :cost => self.class.stretches).to_s
90
+ end
91
+
92
+ module ClassMethods
93
+ Revise::Models.config(self, :pepper, :stretches)
94
+
95
+ def authenticate(email, password)
96
+ account = Account.find_by_email(email)
97
+ return false unless account
98
+ if account.valid_password?(password)
99
+ return account
100
+ else
101
+ return false
102
+ end
103
+ end
104
+ end
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,237 @@
1
+ module Revise
2
+ module Models
3
+ module Invitable
4
+ MAILERS = ['Invitable']
5
+ HELPERS = ['Authentication']
6
+ CONTROLLERS = ['Main', 'Sessions', 'Accounts', 'Invitations']
7
+
8
+ extend ActiveSupport::Concern
9
+
10
+ attr_accessor :skip_invitation
11
+ attr_accessor :completing_invite
12
+
13
+ included do
14
+ include ::Revise::Inviter
15
+
16
+ if Revise.invited_by_class_name
17
+ belongs_to :invited_by, :class_name => Revise.invited_by_class_name
18
+ else
19
+ belongs_to :invited_by, :polymorphic => true
20
+ end
21
+
22
+ include ActiveSupport::Callbacks
23
+ define_callbacks :invitation_accepted
24
+
25
+ attr_accessor :skip_password
26
+
27
+ scope :invitation_not_accepted, where(:invitation_accepted_at => nil)
28
+ scope :invitation_accepted, where(:invitation_accepted_at.ne => nil)
29
+ end
30
+
31
+ def self.required_fields(klass)
32
+ fields = [:invitation_token, :invitation_sent_at, :invitation_accepted_at, :invitation_limit, :invited_by_id,
33
+ :invited_by_type]
34
+ fields -= [:invited_by_type] if Revise.invited_by_class_name
35
+ fields
36
+ end
37
+
38
+ def invitation_fields
39
+ fields = [:invitation_sent_at, :invited_by_id, :invited_by_type]
40
+ fields -= [:invited_by_type] if Revise.invited_by_class_name
41
+ fields
42
+ end
43
+
44
+ # Accept an invitation by clearing invitation token and and setting invitation_accepted_at
45
+ # Confirms it if model is confirmable
46
+ def accept_invitation!
47
+ self.invitation_accepted_at = Time.now.utc
48
+ if self.invited_to_sign_up? && self.valid?
49
+ run_callbacks :invitation_accepted do
50
+ self.invitation_token = nil
51
+ self.confirmed_at = self.invitation_accepted_at if self.respond_to?(:confirmed_at)
52
+ self.save(:validate => false)
53
+ end
54
+ end
55
+ end
56
+
57
+ # Verifies whether a user has been invited or not
58
+ def invited_to_sign_up?
59
+ invitation_token
60
+ end
61
+
62
+ # Verifies whether a user accepted an invitation (or is accepting it)
63
+ def invitation_accepted?
64
+ invitation_accepted_at
65
+ end
66
+
67
+ # Verifies whether a user has accepted an invitation (or is accepting it), or was never invited
68
+ def accepted_or_not_invited?
69
+ invitation_accepted? || !invited_to_sign_up?
70
+ end
71
+
72
+ # Reset invitation token and send invitation again
73
+ def invite!(invited_by = nil)
74
+ was_invited = invited_to_sign_up?
75
+
76
+ # Required to workaround confirmable model's confirmation_required? method
77
+ # being implemented to check for non-nil value of confirmed_at
78
+ if self.new_record? && self.respond_to?(:confirmation_required?)
79
+ def self.confirmation_required?; false; end
80
+ end
81
+
82
+ generate_invitation_token if self.invitation_token.nil?
83
+ self.invitation_sent_at = Time.now.utc
84
+ self.invited_by = invited_by if invited_by
85
+
86
+ # Call these before_validate methods since we aren't validating on save
87
+ self.downcase_keys if self.new_record? && self.respond_to?(:downcase_keys)
88
+ self.strip_whitespace if self.new_record? && self.respond_to?(:strip_whitespace)
89
+
90
+ if save(:validate => false)
91
+ self.invited_by.decrement_invitation_limit! if !was_invited and self.invited_by.present?
92
+ deliver_invitation unless @skip_invitation
93
+ end
94
+ end
95
+
96
+ # Verify whether a invitation is active or not. If the user has been
97
+ # invited, we need to calculate if the invitation time has not expired
98
+ # for this user, in other words, if the invitation is still valid.
99
+ def valid_invitation?
100
+ invited_to_sign_up? && invitation_period_valid?
101
+ end
102
+
103
+ # Only verify password when is not invited
104
+ def valid_password?(password)
105
+ super unless invited_to_sign_up?
106
+ end
107
+
108
+ def reset_password!(new_password, new_password_confirmation)
109
+ super
110
+ accept_invitation!
111
+ end
112
+
113
+ def invite_key_valid?
114
+ return true unless self.class.invite_key.is_a? Hash # FIXME: remove this line when deprecation is removed
115
+ self.class.invite_key.all? do |key, regexp|
116
+ regexp.nil? || self.send(key).try(:match, regexp)
117
+ end
118
+ end
119
+
120
+ def password_required?
121
+ !@skip_password && (encrypted_password.blank? || password.present?)
122
+ end
123
+
124
+ protected
125
+ # Deliver the invitation email
126
+ def deliver_invitation
127
+ send_revise_notification(:invitable, :invitation_instructions, self.name, self.email, self.invitation_token)
128
+ end
129
+
130
+ # Checks if the invitation for the user is within the limit time.
131
+ # We do this by calculating if the difference between today and the
132
+ # invitation sent date does not exceed the invite for time configured.
133
+ # Invite_for is a model configuration, must always be an integer value.
134
+ #
135
+ # Example:
136
+ #
137
+ # # invite_for = 1.day and invitation_sent_at = today
138
+ # invitation_period_valid? # returns true
139
+ #
140
+ # # invite_for = 5.days and invitation_sent_at = 4.days.ago
141
+ # invitation_period_valid? # returns true
142
+ #
143
+ # # invite_for = 5.days and invitation_sent_at = 5.days.ago
144
+ # invitation_period_valid? # returns false
145
+ #
146
+ # # invite_for = nil
147
+ # invitation_period_valid? # will always return true
148
+ #
149
+ def invitation_period_valid?
150
+ invitation_sent_at && (self.class.invite_for.to_i.zero? || invitation_sent_at.utc >= self.class.invite_for.ago)
151
+ end
152
+
153
+ # Generates a new random token for invitation, and stores the time
154
+ # this token is being generated
155
+ def generate_invitation_token
156
+ self.invitation_token = self.class.invitation_token
157
+ end
158
+
159
+ module ClassMethods
160
+ # Return fields to invite
161
+ def invite_key_fields
162
+ invite_key.keys
163
+ end
164
+
165
+ # Attempt to find a user by it's email. If a record is not found, create a new
166
+ # user and send invitation to it. If user is found, returns the user with an
167
+ # email already exists error.
168
+ # If user is found and still have pending invitation, email is resend unless
169
+ # resend_invitation is set to false
170
+ # Attributes must contain the user email, other attributes will be set in the record
171
+ def _invite(attributes={}, invited_by=nil, &block)
172
+ attributes.symbolize_keys!
173
+ invite_key_array = invite_key_fields
174
+ attributes_hash = {}
175
+ invite_key_array.each do |k,v|
176
+ attributes_hash[k] = attributes.delete(k)
177
+ end
178
+
179
+ invitable = find_or_initialize_with_errors(invite_key_array, attributes_hash)
180
+ invitable.invited_by = invited_by
181
+
182
+ invitable.skip_password = true
183
+ invitable.valid? if self.validate_on_invite
184
+ if invitable.new_record?
185
+ invitable.errors.clear if !self.validate_on_invite and invitable.invite_key_valid?
186
+ elsif !invitable.invited_to_sign_up? || !self.resend_invitation
187
+ invite_key_array.each do |key|
188
+ invitable.errors.add(key, :taken)
189
+ end
190
+ end
191
+
192
+ if invitable.errors.empty?
193
+ yield invitable if block_given?
194
+ mail = invitable.invite!
195
+ end
196
+ [invitable, mail]
197
+ end
198
+
199
+ def invite!(attributes={}, invited_by=nil, &block)
200
+ invitable, mail = _invite(attributes, invited_by, &block)
201
+ invitable
202
+ end
203
+
204
+ def invite_mail!(attributes={}, invited_by=nil, &block)
205
+ invitable, mail = _invite(attributes, invited_by, &block)
206
+ mail
207
+ end
208
+
209
+ # Attempt to find a user by it's invitation_token to set it's password.
210
+ # If a user is found, reset it's password and automatically try saving
211
+ # the record. If not user is found, returns a new user containing an
212
+ # error in invitation_token attribute.
213
+ # Attributes must contain invitation_token, password and confirmation
214
+ def accept_invitation!(attributes={})
215
+ invitable = find_or_initialize_with_error_by(:invitation_token, attributes.delete(:invitation_token))
216
+ invitable.errors.add(:invitation_token, :invalid) if invitable.invitation_token && invitable.persisted? && !invitable.valid_invitation?
217
+ if invitable.errors.empty?
218
+ invitable.attributes = attributes
219
+ invitable.accept_invitation!
220
+ end
221
+ invitable
222
+ end
223
+
224
+ # Generate a token checking if one does not already exist in the database.
225
+ def invitation_token
226
+ generate_token(:invitation_token)
227
+ end
228
+
229
+ Revise::Models.config(self, :invite_for)
230
+ Revise::Models.config(self, :validate_on_invite)
231
+ Revise::Models.config(self, :invitation_limit)
232
+ Revise::Models.config(self, :invite_key)
233
+ Revise::Models.config(self, :resend_invitation)
234
+ end
235
+ end
236
+ end
237
+ end
@@ -0,0 +1,99 @@
1
+ module Revise
2
+ module Models
3
+ module Recoverable
4
+ extend ActiveSupport::Concern
5
+
6
+ MAILERS = ['Recoverable']
7
+ HELPERS = []
8
+ CONTROLLERS = ['Recovery']
9
+
10
+ def self.required_fields(klass)
11
+ [:reset_password_sent_at, :reset_password_token, :email]
12
+ end
13
+
14
+ def reset_password!(new_password, new_password_confirmation)
15
+ self.password = new_password
16
+ self.password_confirmation = new_password_confirmation
17
+
18
+ if valid?
19
+ clear_reset_password_token
20
+ after_password_reset
21
+ end
22
+
23
+ save
24
+ end
25
+
26
+ def send_reset_password_instructions
27
+ generate_reset_password_token! if should_generate_reset_token?
28
+ send_revise_notification(:recoverable, :reset_password_instructions, self.name, self.email, self.reset_password_token)
29
+ end
30
+
31
+ def reset_password_period_valid?
32
+ reset_password_sent_at && reset_password_sent_at.utc >= self.class.reset_password_within.ago
33
+ end
34
+
35
+ # Generates a new random token for reset password
36
+ def generate_reset_password_token
37
+ self.reset_password_token = self.class.reset_password_token
38
+ self.reset_password_sent_at = Time.now.utc
39
+ self.reset_password_token
40
+ end
41
+
42
+ # Resets the reset password token with and save the record without
43
+ # validating
44
+ def generate_reset_password_token!
45
+ generate_reset_password_token && save(:validate => false)
46
+ end
47
+
48
+ protected
49
+ def should_generate_reset_token?
50
+ reset_password_token.nil? || !reset_password_period_valid?
51
+ end
52
+
53
+ # Removes reset_password token
54
+ def clear_reset_password_token
55
+ self.reset_password_token = nil
56
+ self.reset_password_sent_at = nil
57
+ end
58
+
59
+ def after_password_reset
60
+ end
61
+
62
+ module ClassMethods
63
+ # Attempt to find a user by its email. If a record is found, send new
64
+ # password instructions to it. If not user is found, returns a new user
65
+ # with an email not found error.
66
+ # Attributes must contain the user email
67
+ def send_reset_password_instructions(attributes={})
68
+ recoverable = find_or_initialize_with_errors(reset_password_keys, attributes, :not_found)
69
+ recoverable.send_reset_password_instructions if recoverable.persisted?
70
+ recoverable
71
+ end
72
+
73
+ # Generate a token checking if one does not already exist in the database.
74
+ def reset_password_token
75
+ generate_token(:reset_password_token)
76
+ end
77
+
78
+ # Attempt to find a user by its reset_password_token to reset its
79
+ # password. If a user is found and token is still valid, reset its password and automatically
80
+ # try saving the record. If not user is found, returns a new user
81
+ # containing an error in reset_password_token attribute.
82
+ # Attributes must contain reset_password_token, password and confirmation
83
+ def reset_password_by_token(attributes={})
84
+ recoverable = find_or_initialize_with_error_by(:reset_password_token, attributes[:reset_password_token])
85
+ if recoverable.persisted?
86
+ if recoverable.reset_password_period_valid?
87
+ recoverable.reset_password!(attributes[:password], attributes[:password_confirmation])
88
+ else
89
+ recoverable.errors.add(:reset_password_token, :expired)
90
+ end
91
+ end
92
+ recoverable
93
+ end
94
+
95
+ Revise::Models.config(self, :reset_password_keys, :reset_password_within)
96
+ end
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,2 @@
1
+ require 'orm_adapter/adapters/mongo_mapper'
2
+ MongoMapper::Document::ClassMethods.send :include, Revise::Models
@@ -0,0 +1,41 @@
1
+ module Revise
2
+ class ParamFilter
3
+ def initialize(case_insensitive_keys, strip_whitespace_keys)
4
+ @case_insensitive_keys = case_insensitive_keys || []
5
+ @strip_whitespace_keys = strip_whitespace_keys || []
6
+ end
7
+
8
+ def filter(conditions)
9
+ conditions = stringify_params(conditions.dup)
10
+
11
+ @case_insensitive_keys.each do |k|
12
+ value = conditions[k]
13
+ next unless value.respond_to?(:downcase)
14
+ conditions[k] = value.downcase
15
+ end
16
+
17
+ @strip_whitespace_keys.each do |k|
18
+ value = conditions[k]
19
+ next unless value.respond_to?(:strip)
20
+ conditions[k] = value.strip
21
+ end
22
+
23
+ conditions
24
+ end
25
+
26
+ # Force keys to be string to avoid injection on mongoid related database.
27
+ def stringify_params(conditions)
28
+ return conditions unless conditions.is_a?(Hash)
29
+ conditions.each do |k, v|
30
+ conditions[k] = v.to_s if param_requires_string_conversion?(v)
31
+ end
32
+ end
33
+
34
+ private
35
+
36
+ # Determine which values should be transformed to string or passed as-is to the query builder underneath
37
+ def param_requires_string_conversion?(value)
38
+ [Fixnum, TrueClass, FalseClass, Regexp].none? {|clz| value.is_a? clz }
39
+ end
40
+ end
41
+ end