devise_invitable 1.4.1 → 1.5.3

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 3baf83979bda300001727d86af188f93a91bf95c
4
- data.tar.gz: 74d6ac1f55c041e7075e4c1e8ec026e470fdfe12
3
+ metadata.gz: 34b7a909db3f99e89240bf2b81107b5981e656cb
4
+ data.tar.gz: 47f3c423acd3c430232c028e130174c23720081e
5
5
  SHA512:
6
- metadata.gz: 0b08b0b990d88fd99aed53e4d3082db8354bda2e5046fad13901debde5ecb614ba5cdcea7e11709798ea16d4dc99dac27dbd622534179cf1d9a3cbaaaba38bda
7
- data.tar.gz: 3ca119718132addd0b7b331ac4fe81c4e21852b6480ef10c3ed742492b82e86e6fbd01f84957e5a7b6426aba2bbd5eaeb8f65460730399f3e286e125355ca77f
6
+ metadata.gz: fb028f99d4fc760cdecc7f747b3070fa7c2373d3e5655cdb51c2526245a546f1c8b4b84175810b9b8c1df33f054a0b5b1c4190f2280eda5d31f6f7377e7cffbf
7
+ data.tar.gz: e04a795483022a82a601790ad24d682934322e45348026bd6a3b387b8284b41ca8403e55afddaf57b51d630961cb2650e8fbcea15d9144c07d3e1fde8f23976f
data/CHANGELOG CHANGED
@@ -1,9 +1,69 @@
1
+ = 1.5.2
2
+
3
+ - Fix #571, accept invitation when password changes only if reset_password_token was present
4
+ - Add support for setting invited_by foreign key
5
+ - Set random initial password for invited users
6
+ - Don't override password while User.invite!
7
+
8
+ = 1.5.1
9
+
10
+ - Fix #562 Avoid using after_password_reset
11
+ - Fix #564
12
+
13
+ Compare: https://github.com/scambra/devise_invitable/compare/v1.5.0...v1.5.1
14
+
15
+ = 1.5.0
16
+
17
+ Override valid_password? and unauthenticated_message instead of active_for_authentication? and inactive_message, active_for_authentication? doesn't work for default behavior of invited users without password
18
+
19
+ - Get list & check for user(s) created by invite irrespective of status
20
+ - Update simple_form template's hash syntax
21
+ - Update migration template's hash syntax
22
+ - Check if after_password_reset is defined
23
+
24
+ Compare: https://github.com/scambra/devise_invitable/compare/v1.4.2...v1.5.0
25
+
26
+ = 1.4.2
27
+
28
+ - Add option to allow controlling of auto sign in functionality for security
29
+ - Add intermediate method in active_for_authentication? for more flexibility
30
+ - Add intermediate block_from_invitation? method for more flexibility
31
+ - Fix undefined method `invite_key_fields'
32
+ - Fix override valid_password? instead of active_for_authentication? (fixes #541)
33
+
34
+ Compare: https://github.com/scambra/devise_invitable/compare/v1.4.1...v1.4.2
35
+
36
+ = 1.4.1
37
+
38
+ - Begin testing against devise 3.4
39
+ - Use current_inviter to get redirect path after invite
40
+ (https://github.com/scambra/devise_invitable/pull/523)
41
+
42
+ Compare: https://github.com/scambra/devise_invitable/compare/v1.4.0...v1.4.1
43
+
1
44
  = 1.4.0
45
+
2
46
  Override active_for_authentication? and inactive_message instead of valid_password?
3
47
  To use counter_cache, invited_by_counter_cache must be set, no more checking of invitations_count to enable counter cache
4
48
 
49
+ Compare: https://github.com/scambra/devise_invitable/compare/v1.3.6...v1.4.0
50
+
51
+ = 1.3.6
52
+
53
+ - Regenerate invitation token each time even if "skip_invitation" was true
54
+ - Add passing a block to instance #invite! method
55
+ - Improvements to tests
56
+
57
+ Compare: https://github.com/scambra/devise_invitable/compare/v1.3.5...v1.3.6
58
+
59
+ = 1.3.5
60
+
61
+ No notes yet, contributions welcome.
62
+
5
63
  = 1.3.0
64
+
6
65
  Now devise 3.1 compatible, @token must be used instead of @resource.invitation_token in mail views
7
66
 
8
67
  = 1.2.0
68
+
9
69
  Add invitation_created_at column which is set when invitation is created even when sending is skipped. This new field is used to check invitation period valid
data/README.rdoc CHANGED
@@ -1,9 +1,9 @@
1
1
  = DeviseInvitable
2
- {<img src="https://travis-ci.org/scambra/devise_invitable.png"/>}[http://travis-ci.org/scambra/devise_invitable]
2
+ {<img src="https://travis-ci.org/scambra/devise_invitable.png"/>}[https://travis-ci.org/scambra/devise_invitable] {<img src="https://codeclimate.com/github/scambra/devise_invitable/badges/gpa.svg"/}[https://codeclimate.com/github/scambra/devise_invitable]
3
3
 
4
- It adds support to devise[http://github.com/plataformatec/devise] for sending invitations by email (it requires to be authenticated) and accept the invitation setting the password.
4
+ It adds support to devise[https://github.com/plataformatec/devise] for sending invitations by email (it requires to be authenticated) and accept the invitation setting the password.
5
5
 
6
- DeviseInvitable currently supports Rails 3 and 4, if you want to use it with Rails 2.3 you must install version {0.2.3}[http://rubygems.org/gems/devise_invitable/versions/0.2.3]
6
+ DeviseInvitable currently supports Rails 3 and 4, if you want to use it with Rails 2.3 you must install version {0.2.3}[https://rubygems.org/gems/devise_invitable/versions/0.2.3]
7
7
 
8
8
  If you want to use devise 3.0.x, you must use 1.2.1, newer versions require devise >= 3.1.0
9
9
 
@@ -15,8 +15,8 @@ Install DeviseInvitable gem, it will also install dependencies (such as devise a
15
15
 
16
16
  Add DeviseInvitable to your Gemfile (and Devise if you weren't using them):
17
17
 
18
- gem 'devise', '>= 2.0.0'
19
- gem 'devise_invitable', '~> 1.3.4'
18
+ gem 'devise', '~> 3.5.2'
19
+ gem 'devise_invitable', '~> 1.5.2'
20
20
 
21
21
  === Automatic installation
22
22
 
@@ -28,7 +28,7 @@ When you are done, you are ready to add DeviseInvitable to any of your Devise mo
28
28
 
29
29
  rails generate devise_invitable MODEL
30
30
 
31
- Replace MODEL by the class name you want to add DeviseInvitable, like User, Admin, etc. This will add the :invitable flag to your model's Devise modules. The generator will also create a migration file (if your ORM support them). Continue reading this file to understand exactly what the generator produces and how to use it.
31
+ Replace MODEL by the class name you want to add DeviseInvitable, like User, Admin, etc. This will add the :invitable flag to your model's Devise modules. The generator will also create a migration file (if your ORM supports them). Continue reading this file to understand exactly what the generator produces and how to use it.
32
32
 
33
33
  === Manual installation
34
34
 
@@ -132,6 +132,10 @@ or directly as parameters to the <tt>devise</tt> method:
132
132
 
133
133
  * invited_by_class_name: The class name of the inviting model. If this is nil, polymorphic association is used.
134
134
 
135
+ * invited_by_foreign_key: The foreign key to the inviting model (only used if invited_by_class_name is set, otherwise :invited_by_id)
136
+
137
+ * allow_insecure_sign_in_after_accept: automatically sign in the user after they set a password. Enabled by default.
138
+
135
139
  For more details, see <tt>config/initializers/devise.rb</tt> (after you invoked the "devise_invitable:install" generator described above).
136
140
 
137
141
  == Configuring views
@@ -148,11 +152,11 @@ Then turn scoped views on in config/initializers/devise.rb:
148
152
 
149
153
  config.scoped_views = true
150
154
 
151
- Please refer to {Devise's README}[http://github.com/plataformatec/devise] for more information about views.
155
+ Please refer to {Devise's README}[https://github.com/plataformatec/devise] for more information about views.
152
156
 
153
157
  == Configuring controllers
154
158
 
155
- To change the controller's behavior, create a controller that inherits from <tt>Devise::InvitationsController</tt>. The available methods are: new, create, edit, and update. You should read the {original controllers source}[https://raw.github.com/scambra/devise_invitable/master/app/controllers/devise/invitations_controller.rb] before editing any of these actions. Your controller might now look something like this:
159
+ To change the controller's behavior, create a controller that inherits from <tt>Devise::InvitationsController</tt>. The available methods are: new, create, edit, and update. You should read the {original controllers source}[https://github.com/scambra/devise_invitable/blob/master/app/controllers/devise/invitations_controller.rb] before editing any of these actions. Your controller might now look something like this:
156
160
 
157
161
  class Users::InvitationsController < Devise::InvitationsController
158
162
  def update
@@ -198,7 +202,7 @@ To change behaviour of inviting or accepting users, you can simply override two
198
202
 
199
203
  == Strong Parameters
200
204
 
201
- When you customize your own views, you may end up adding new attributes to forms. Rails 4 moved the parameter sanitization from the model to the controller, causing DeviseInvitable to handle this concern at the controller as well. Read about it in {devise README}[http://github.com/plataformatec/devise#strong-parameters]
205
+ When you customize your own views, you may end up adding new attributes to forms. Rails 4 moved the parameter sanitization from the model to the controller, causing DeviseInvitable to handle this concern at the controller as well. Read about it in {devise README}[https://github.com/plataformatec/devise#strong-parameters]
202
206
 
203
207
  There are just two actions in DeviseInvitable that allows any set of parameters to be passed down to the model, therefore requiring sanitization. Their names and the permited parameters by default are:
204
208
 
@@ -254,9 +258,11 @@ You can add :skip_invitation to attributes hash if skip_invitation is added to a
254
258
  User.invite!(:email => "new_user@example.com", :name => "John Doe", :skip_invitation => true)
255
259
  # => the record will be created, but the invitation email will not be sent
256
260
 
257
- Skip_invitation skips sending the email, but sets invitation_token, so invited_to_sign_up? on the
261
+ Skip_invitation skips sending the email, but sets invitation_token, so <tt>invited_to_sign_up?</tt> on the
258
262
  resulting user returns true.
259
263
 
264
+ To check if a perticular user is created by invitation, irrespective to state of invitation one can use <tt>created_by_invite?</tt>
265
+
260
266
  **Warning**
261
267
 
262
268
  When using skip_invitation you must send the email with the user object instance that generated the tokens, as
@@ -295,8 +301,9 @@ The callbacks support all options and arguments available to the standard callba
295
301
 
296
302
  A pair of scopes to find those users that have accepted, and those that have not accepted, invitations are defined:
297
303
 
298
- User.invitation_accepted # => returns all Users for whom the invitation_accepted_at attribute is not nil
304
+ User.invitation_accepted # => returns all Users for whom the invitation_accepted_at attribute is not nil
299
305
  User.invitation_not_accepted # => returns all Users for whom the invitation_accepted_at attribute is nil
306
+ User.created_by_invite # => returns all Users who are created by invitations, irrespective to invitation status
300
307
 
301
308
  == Integration in a Rails application
302
309
 
@@ -305,7 +312,7 @@ Instead, in your views, put a link to <tt>new_user_invitation_path</tt> or <tt>n
305
312
 
306
313
  After an invitation is created and sent, the inviter will be redirected to after_invite_path_for(resource_name), which is stored path or the same path as after_sign_in_path_for by default.
307
314
 
308
- After an invitation is accepted, the invitee will be redirected to after_accept_path_for(resource), which is the same path as after_sign_in_path_for by default. If you want to override the path, override invitations controller and define after_accept_path_for method. This is useful in the common case that a user is invited to a specific location in your application. More on {Devise's README}[http://github.com/plataformatec/devise], "Controller filters and helpers" section.
315
+ After an invitation is accepted, the invitee will be redirected to after_accept_path_for(resource), which is the same path as after_sign_in_path_for by default. If you want to override the path, override invitations controller and define after_accept_path_for method. This is useful in the common case that a user is invited to a specific location in your application. More on {Devise's README}[https://github.com/plataformatec/devise], "Controller filters and helpers" section.
309
316
 
310
317
  The invitation email includes a link to accept the invitation that looks like this: <tt>/users/invitation/accept?invitation_token=abcd123</tt>. When clicked, the invited must set a password in order to accept its invitation. Note that if the invitation_token is not present or not valid, the invited is redirected to after_sign_out_path_for(resource_name).
311
318
 
@@ -355,6 +362,7 @@ DeviseInvitable uses flash messages with I18n with the flash keys <tt>:send_inst
355
362
  send_instructions: 'An invitation email has been sent to %{email}.'
356
363
  invitation_token_invalid: 'The invitation token provided is not valid!'
357
364
  updated: 'Your password was set successfully. You are now signed in.'
365
+ updated_not_active: 'Your password was set successfully.'
358
366
 
359
367
  You can also create distinct messages based on the resource you've configured using the singular name given in routes:
360
368
 
@@ -365,6 +373,7 @@ You can also create distinct messages based on the resource you've configured us
365
373
  send_instructions: 'A new user invitation has been sent to %{email}.'
366
374
  invitation_token_invalid: 'Your invitation token is not valid!'
367
375
  updated: 'Welcome on board! You are now signed in.'
376
+ updated_not_active: 'Welcome on board! Sign in to continue.'
368
377
 
369
378
  The DeviseInvitable mailer uses the same pattern as Devise to create mail subject messages:
370
379
 
@@ -402,9 +411,9 @@ To test DeviseInvitable for the ActiveRecord ORM with RVM, Ruby 1.9.2, and Rubyg
402
411
 
403
412
  Check them all at:
404
413
 
405
- http://github.com/scambra/devise_invitable/contributors
414
+ https://github.com/scambra/devise_invitable/contributors
406
415
 
407
- Special thanks to rymai[http://github.com/rymai] for the Rails 3 support, his fork was a great help.
416
+ Special thanks to rymai[https://github.com/rymai] for the Rails 3 support, his fork was a great help.
408
417
 
409
418
  == Note on Patches/Pull Requests
410
419
 
@@ -37,17 +37,24 @@ class Devise::InvitationsController < DeviseController
37
37
 
38
38
  # PUT /resource/invitation
39
39
  def update
40
+ raw_invitation_token = update_resource_params[:invitation_token]
40
41
  self.resource = accept_resource
41
42
  invitation_accepted = resource.errors.empty?
42
43
 
43
44
  yield resource if block_given?
44
45
 
45
46
  if invitation_accepted
46
- flash_message = resource.active_for_authentication? ? :updated : :updated_not_active
47
- set_flash_message :notice, flash_message if is_flashing_format?
48
- sign_in(resource_name, resource)
49
- respond_with resource, :location => after_accept_path_for(resource)
47
+ if Devise.allow_insecure_sign_in_after_accept
48
+ flash_message = resource.active_for_authentication? ? :updated : :updated_not_active
49
+ set_flash_message :notice, flash_message if is_flashing_format?
50
+ sign_in(resource_name, resource)
51
+ respond_with resource, :location => after_accept_path_for(resource)
52
+ else
53
+ set_flash_message :notice, :updated_not_active if is_flashing_format?
54
+ respond_with resource, :location => new_session_path(resource_name)
55
+ end
50
56
  else
57
+ resource.invitation_token = raw_invitation_token
51
58
  respond_with_navigational(resource){ render :edit }
52
59
  end
53
60
  end
@@ -4,11 +4,13 @@ class DeviseInvitable::RegistrationsController < Devise::RegistrationsController
4
4
  def build_resource(hash = nil)
5
5
  hash ||= resource_params || {}
6
6
  if hash[:email]
7
- self.resource = resource_class.where(:email => hash[:email], :encrypted_password => '').first
8
- if self.resource
7
+ self.resource = resource_class.where(:email => hash[:email]).first
8
+ if self.resource && self.resource.respond_to?(:invited_to_sign_up?) && self.resource.invited_to_sign_up?
9
9
  self.resource.attributes = hash
10
10
  self.resource.send_confirmation_instructions if self.resource.confirmation_required_for_invited?
11
11
  self.resource.accept_invitation
12
+ else
13
+ self.resource = nil
12
14
  end
13
15
  end
14
16
  self.resource ||= super
@@ -6,6 +6,7 @@ en:
6
6
  send_instructions: "An invitation email has been sent to %{email}."
7
7
  invitation_token_invalid: "The invitation token provided is not valid!"
8
8
  updated: "Your password was set successfully. You are now signed in."
9
+ updated_not_active: "Your password was set successfully."
9
10
  no_invitations_remaining: "No invitations remaining"
10
11
  invitation_removed: "Your invitation was removed."
11
12
  new:
@@ -34,6 +34,9 @@ module Devise
34
34
  else
35
35
  {:polymorphic => true}
36
36
  end
37
+ if fk = Devise.invited_by_foreign_key
38
+ belongs_to_options[:foreign_key] = fk
39
+ end
37
40
  if defined?(ActiveRecord) && defined?(ActiveRecord::Base) && self < ActiveRecord::Base
38
41
  counter_cache = Devise.invited_by_counter_cache
39
42
  belongs_to_options.merge! :counter_cache => counter_cache if counter_cache
@@ -43,13 +46,13 @@ module Devise
43
46
  include ActiveSupport::Callbacks
44
47
  define_callbacks :invitation_accepted
45
48
 
46
- attr_writer :skip_password
47
-
48
49
  scope :no_active_invitation, lambda { where(:invitation_token => nil) }
49
50
  if defined?(Mongoid) && defined?(Mongoid::Document) && self < Mongoid::Document
51
+ scope :created_by_invite, lambda { where(:invitation_sent_at.ne => nil) }
50
52
  scope :invitation_not_accepted, lambda { where(:invitation_accepted_at => nil, :invitation_token.ne => nil) }
51
53
  scope :invitation_accepted, lambda { where(:invitation_accepted_at.ne => nil) }
52
54
  else
55
+ scope :created_by_invite, lambda { where(arel_table[:invitation_sent_at].not_eq(nil)) }
53
56
  scope :invitation_not_accepted, lambda { where(arel_table[:invitation_token].not_eq(nil)).where(:invitation_accepted_at => nil) }
54
57
  scope :invitation_accepted, lambda { where(arel_table[:invitation_accepted_at].not_eq(nil)) }
55
58
 
@@ -61,7 +64,7 @@ module Devise
61
64
 
62
65
  def self.required_fields(klass)
63
66
  fields = [:invitation_token, :invitation_created_at, :invitation_sent_at, :invitation_accepted_at,
64
- :invitation_limit, :invited_by_id, :invited_by_type]
67
+ :invitation_limit, Devise.invited_by_foreign_key || :invited_by_id, :invited_by_type]
65
68
  fields << :invitations_count if defined?(ActiveRecord) && self < ActiveRecord::Base
66
69
  fields -= [:invited_by_type] if Devise.invited_by_class_name
67
70
  fields
@@ -85,6 +88,11 @@ module Devise
85
88
  end
86
89
  end
87
90
 
91
+ # Verify wheather a user is created by invitation, irrespective to invitation status
92
+ def created_by_invite?
93
+ invitation_sent_at.present?
94
+ end
95
+
88
96
  # Verifies whether a user has been invited or not
89
97
  def invited_to_sign_up?
90
98
  persisted? && invitation_token.present?
@@ -102,23 +110,24 @@ module Devise
102
110
 
103
111
  # Reset invitation token and send invitation again
104
112
  def invite!(invited_by = nil)
113
+ # This is an order-dependant assignment, this can't be moved
105
114
  was_invited = invited_to_sign_up?
106
115
 
107
116
  # Required to workaround confirmable model's confirmation_required? method
108
117
  # being implemented to check for non-nil value of confirmed_at
109
- if self.new_record? && self.respond_to?(:confirmation_required?, true)
118
+ if new_record_and_responds_to?(:confirmation_required?)
110
119
  def self.confirmation_required?; false; end
111
120
  end
112
121
 
113
122
  yield self if block_given?
114
- generate_invitation_token if self.invitation_token.nil? || (!skip_invitation || @raw_invitation_token.nil?)
123
+ generate_invitation_token if no_token_present_or_skip_invitation?
115
124
  self.invitation_created_at = Time.now.utc
116
125
  self.invitation_sent_at = self.invitation_created_at unless skip_invitation
117
126
  self.invited_by = invited_by if invited_by
118
127
 
119
128
  # Call these before_validate methods since we aren't validating on save
120
- self.downcase_keys if self.new_record? && self.respond_to?(:downcase_keys, true)
121
- self.strip_whitespace if self.new_record? && self.respond_to?(:strip_whitespace, true)
129
+ self.downcase_keys if new_record_and_responds_to?(:downcase_keys)
130
+ self.strip_whitespace if new_record_and_responds_to?(:strip_whitespace)
122
131
 
123
132
  if save(:validate => false)
124
133
  self.invited_by.decrement_invitation_limit! if !was_invited and self.invited_by.present?
@@ -134,17 +143,18 @@ module Devise
134
143
  end
135
144
 
136
145
  # Only verify password when is not invited
137
- def active_for_authentication?
138
- super unless invited_to_sign_up?
146
+ def valid_password?(password)
147
+ super unless block_from_invitation?
139
148
  end
140
149
 
141
- def inactive_message
142
- invited_to_sign_up? ? :invited : super
150
+ def unauthenticated_message
151
+ block_from_invitation? ? :invited : super
143
152
  end
144
153
 
145
- def after_password_reset
154
+ def clear_reset_password_token
155
+ reset_password_token_present = reset_password_token.present?
146
156
  super
147
- accept_invitation! if invited_to_sign_up?
157
+ accept_invitation! if reset_password_token_present && invited_to_sign_up?
148
158
  end
149
159
 
150
160
  def clear_errors_on_valid_keys
@@ -170,13 +180,9 @@ module Devise
170
180
  end
171
181
 
172
182
  protected
173
- # Overriding the method in Devise's :validatable module so password is not required on inviting
174
- def password_required?
175
- !skip_password && super
176
- end
177
183
 
178
- def skip_password
179
- @skip_password ||= false
184
+ def block_from_invitation?
185
+ invited_to_sign_up?
180
186
  end
181
187
 
182
188
  # Checks if the invitation for the user is within the limit time.
@@ -215,6 +221,14 @@ module Devise
215
221
  generate_invitation_token && save(:validate => false)
216
222
  end
217
223
 
224
+ def new_record_and_responds_to?(method)
225
+ self.new_record? && self.respond_to?(method, true)
226
+ end
227
+
228
+ def no_token_present_or_skip_invitation?
229
+ self.invitation_token.nil? || (!skip_invitation || @raw_invitation_token.nil?)
230
+ end
231
+
218
232
  module ClassMethods
219
233
  # Return fields to invite
220
234
  def invite_key_fields
@@ -240,8 +254,10 @@ module Devise
240
254
  invitable = find_or_initialize_with_errors(invite_key_array, attributes_hash)
241
255
  invitable.assign_attributes(attributes)
242
256
  invitable.invited_by = invited_by
257
+ unless invitable.password || invitable.encrypted_password.present?
258
+ invitable.password = Devise.friendly_token[0, 20]
259
+ end
243
260
 
244
- invitable.skip_password = true
245
261
  invitable.valid? if self.validate_on_invite
246
262
  if invitable.new_record?
247
263
  invitable.clear_errors_on_valid_keys if !self.validate_on_invite
@@ -284,7 +300,6 @@ module Devise
284
300
 
285
301
  invitable = find_or_initialize_with_error_by(:invitation_token, invitation_token)
286
302
  invitable.errors.add(:invitation_token, :invalid) if invitable.invitation_token && invitable.persisted? && !invitable.valid_invitation?
287
- invitable.invitation_token = original_token
288
303
  invitable unless only_valid && invitable.errors.present?
289
304
  end
290
305
 
@@ -303,6 +318,8 @@ module Devise
303
318
  Devise::Models.config(self, :invitation_limit)
304
319
  Devise::Models.config(self, :invite_key)
305
320
  Devise::Models.config(self, :resend_invitation)
321
+ Devise::Models.config(self, :allow_insecure_sign_in_after_accept)
322
+
306
323
  end
307
324
  end
308
325
  end
@@ -20,7 +20,7 @@ module DeviseInvitable
20
20
  def attributes_for_with_invitable(kind)
21
21
  case kind
22
22
  when :invite
23
- resource_class.invite_key_fields
23
+ resource_class.respond_to?(:invite_key_fields) ? resource_class.invite_key_fields : []
24
24
  when :accept_invitation
25
25
  [:password, :password_confirmation, :invitation_token]
26
26
  else attributes_for_without_invitable(kind)
@@ -1,3 +1,3 @@
1
1
  module DeviseInvitable
2
- VERSION = '1.4.1'
2
+ VERSION = '1.5.3'
3
3
  end
@@ -62,10 +62,20 @@ module Devise
62
62
  mattr_accessor :invited_by_class_name
63
63
  @@invited_by_class_name = nil
64
64
 
65
+ # Public: The foreign key to the inviting model (if invited_by_class_name is set)
66
+ # (default: :invited_by_id)
67
+ mattr_accessor :invited_by_foreign_key
68
+ @@invited_by_foreign_key = nil
69
+
65
70
  # Public: The column name used for counter_cache column. If this is nil,
66
71
  # the #invited_by association is declared without counter_cache. (default: nil)
67
72
  mattr_accessor :invited_by_counter_cache
68
73
  @@invited_by_counter_cache = nil
74
+
75
+ # Public: Auto-login after the user accepts the invite. If this is false,
76
+ # the user will need to manually log in after accepting the invite (default: true).
77
+ mattr_accessor :allow_insecure_sign_in_after_accept
78
+ @@allow_insecure_sign_in_after_accept = true
69
79
  end
70
80
 
71
81
  Devise.add_module :invitable, :controller => :invitations, :model => 'devise_invitable/model', :route => {:invitation => [nil, :new, :accept]}
@@ -6,28 +6,18 @@ class DeviseInvitableAddTo<%= table_name.camelize %> < ActiveRecord::Migration
6
6
  t.datetime :invitation_sent_at
7
7
  t.datetime :invitation_accepted_at
8
8
  t.integer :invitation_limit
9
- t.references :invited_by, :polymorphic => true
9
+ t.references :invited_by, polymorphic: true
10
10
  t.integer :invitations_count, default: 0
11
11
  t.index :invitations_count
12
- t.index :invitation_token, :unique => true # for invitable
12
+ t.index :invitation_token, unique: true # for invitable
13
13
  t.index :invited_by_id
14
14
  end
15
-
16
- # And allow null encrypted_password and password_salt:
17
- change_column_null :<%= table_name %>, :encrypted_password, true
18
- <% if class_name.constantize.columns_hash['password_salt'] -%>
19
- change_column_null :<%= table_name %>, :password_salt, true
20
- <% end -%>
21
15
  end
22
16
 
23
17
  def down
24
18
  change_table :<%= table_name %> do |t|
25
- t.remove_references :invited_by, :polymorphic => true
19
+ t.remove_references :invited_by, polymorphic: true
26
20
  t.remove :invitations_count, :invitation_limit, :invitation_sent_at, :invitation_accepted_at, :invitation_token, :invitation_created_at
27
21
  end
28
- change_column_null :<%= table_name %>, :encrypted_password, false
29
- <% if class_name.constantize.columns_hash['password_salt'] -%>
30
- change_column_null :<%= table_name %>, :password_salt,false
31
- <% end -%>
32
22
  end
33
23
  end
@@ -52,11 +52,20 @@ module DeviseInvitable
52
52
  # Default: nil
53
53
  # config.invited_by_class_name = 'User'
54
54
 
55
+ # The foreign key to the inviting model (if invited_by_class_name is set)
56
+ # Default: :invited_by_id
57
+ # config.invited_by_foreign_key = :invited_by_id
58
+
55
59
  # The column name used for counter_cache column. If this is nil,
56
60
  # the #invited_by association is declared without counter_cache.
57
61
  # Default: nil
58
62
  # config.invited_by_counter_cache = :invitations_count
59
63
 
64
+ # Auto-login after the user accepts the invite. If this is false,
65
+ # the user will need to manually log in after accepting the invite.
66
+ # Default: false
67
+ # config.allow_insecure_sign_in_after_accept = true
68
+
60
69
  CONTENT
61
70
  end
62
71
  end
@@ -1,6 +1,6 @@
1
1
  <h2><%= t 'devise.invitations.edit.header' %></h2>
2
2
 
3
- <%= simple_form_for resource, :as => resource_name, :url => invitation_path(resource_name), :html => { :method => :put } do |f| %>
3
+ <%= simple_form_for resource, as: resource_name, url: invitation_path(resource_name), html: { method: :put } do |f| %>
4
4
  <%= devise_error_messages! %>
5
5
  <%= f.hidden_field :invitation_token %>
6
6
 
@@ -1,6 +1,6 @@
1
1
  <h2><%= t "devise.invitations.new.header" %></h2>
2
2
 
3
- <%= simple_form_for resource, :as => resource_name, :url => invitation_path(resource_name), :html => {:method => :post} do |f| %>
3
+ <%= simple_form_for resource, as: resource_name, url: invitation_path(resource_name), html: { method: :post} do |f| %>
4
4
  <%= devise_error_messages! %>
5
5
 
6
6
  <% resource.class.invite_key_fields.each do |field| -%>
@@ -25,7 +25,9 @@ class DeviseInvitable::RegistrationsControllerTest < ActionController::TestCase
25
25
  sign_out @issuer
26
26
 
27
27
  @invitee = User.where(:email => invitee_email).first
28
- assert @invitee.encrypted_password.blank?, "the password should be unset"
28
+ assert_nil @invitee.invitation_accepted_at
29
+ assert_not_nil @invitee.invitation_token
30
+ assert !@invitee.confirmed?
29
31
 
30
32
  # sign_up the invitee
31
33
  assert_difference('ActionMailer::Base.deliveries.size') do
@@ -47,18 +49,63 @@ class DeviseInvitable::RegistrationsControllerTest < ActionController::TestCase
47
49
  assert_nil @invitee.invitation_token
48
50
  assert @invitee.invited_by_id.present?
49
51
  assert @invitee.invited_by_type.present?
50
- assert !@invitee.confirmed?
51
52
  assert @invitee.confirmation_token.present?
53
+ assert !@invitee.confirmed?
54
+ end
55
+
56
+ test "non-invited users may still sign up directly by themselves" do
57
+ register_email = "invitee@example.org"
58
+ # sign_up the invitee
59
+ assert_difference('ActionMailer::Base.deliveries.size') do
60
+ post :create, :user => {:email => register_email, :password => "1password", :bio => '.'}
61
+ end
62
+ assert_nil @controller.current_user
63
+
64
+ @user = User.where(:email => register_email).first
65
+
66
+ # do not send emails on model changes
67
+ assert_no_difference('ActionMailer::Base.deliveries.size') do
68
+ @user.bio = "I am a robot"
69
+ @user.save!
70
+ @user.bio = "I am a human"
71
+ @user.save!
72
+ end
73
+
74
+ assert @user.encrypted_password.present?
75
+ assert_nil @user.invitation_accepted_at
76
+ assert_nil @user.invitation_token
77
+ assert_nil @user.invited_by
78
+ assert @user.confirmation_token.present?
79
+ assert !@user.confirmed?
52
80
  end
53
81
 
54
82
  test "not invitable resources can register" do
55
83
  @request.env["devise.mapping"] = Devise.mappings[:admin]
56
84
  invitee_email = "invitee@example.org"
57
85
 
86
+ assert_nil Admin.where(:email => invitee_email).first
87
+
88
+ post :create, :admin => {:email => invitee_email, :password => "1password"}
89
+
90
+ @invitee = Admin.where(:email => invitee_email).first
91
+ assert @invitee.encrypted_password.present?
92
+ end
93
+
94
+ test "not invitable resources are not logged in after sign up again" do
95
+ @request.env["devise.mapping"] = Devise.mappings[:admin]
96
+ invitee_email = "invitee@example.org"
97
+
58
98
  post :create, :admin => {:email => invitee_email, :password => "1password"}
99
+ assert_response 302
59
100
 
60
101
  @invitee = Admin.where(:email => invitee_email).first
61
102
  assert @invitee.encrypted_password.present?
103
+
104
+ sign_out @invitee
105
+ post :create, :admin => {:email => invitee_email, :password => "2password"}
106
+ assert_response 200
107
+ assert_equal @invitee.encrypted_password, Admin.where(:email => invitee_email).first.encrypted_password
108
+ assert @controller.send(:resource).errors.present?
62
109
  end
63
110
 
64
111
  test "missing params on a create should not cause an error" do
@@ -64,6 +64,25 @@ class InvitationTest < ActionDispatch::IntegrationTest
64
64
  assert_equal root_path, current_path
65
65
  end
66
66
 
67
+ test 'invited user without password should not be able to sign in' do
68
+ user = User.invite!(:email => "valid@email.com")
69
+ user.password = 'test'
70
+ sign_in_as_user user
71
+
72
+ assert_equal new_user_session_path, current_path
73
+ assert page.has_css?('p#alert', :text => 'You have a pending invitation, accept it to finish creating your account.')
74
+ end
75
+
76
+ test 'invited user with password should not be able to sign in' do
77
+ user = User.invite!(:email => "valid@email.com")
78
+ user.password = '987654321'
79
+ user.save
80
+ sign_in_as_user user
81
+
82
+ assert_equal new_user_session_path, current_path
83
+ assert page.has_css?('p#alert', :text => 'You have a pending invitation, accept it to finish creating your account.')
84
+ end
85
+
67
86
  test 'not authenticated user with invalid invitation token should not be able to set his password' do
68
87
  user = User.invite!(:email => "valid@email.com")
69
88
  user.accept_invitation!
@@ -80,7 +99,7 @@ class InvitationTest < ActionDispatch::IntegrationTest
80
99
  end
81
100
  assert_equal user_invitation_path, current_path
82
101
  assert page.has_css?('#error_explanation li', :text => /Password .*doesn\'t match/)
83
- assert user.encrypted_password.blank?
102
+ assert !user.confirmed?
84
103
  end
85
104
 
86
105
  test 'not authenticated user with valid data should be able to change his password' do
@@ -90,6 +109,7 @@ class InvitationTest < ActionDispatch::IntegrationTest
90
109
  assert_equal root_path, current_path
91
110
  assert page.has_css?('p#notice', :text => 'Your password was set successfully. You are now signed in.')
92
111
  assert user.reload.valid_password?('987654321')
112
+ assert user.confirmed?
93
113
  end
94
114
 
95
115
  test 'after entering invalid data user should still be able to set his password' do
@@ -99,17 +119,32 @@ class InvitationTest < ActionDispatch::IntegrationTest
99
119
  end
100
120
  assert_equal user_invitation_path, current_path
101
121
  assert page.has_css?('#error_explanation')
102
- assert user.encrypted_password.blank?
103
122
 
104
123
  set_password :visit => false
105
124
  assert page.has_css?('p#notice', :text => 'Your password was set successfully. You are now signed in.')
106
125
  assert user.reload.valid_password?('987654321')
107
126
  end
108
127
 
109
- test 'sign in user automatically after setting it\'s password' do
128
+ test 'sign in user automatically after setting it\'s password if config.allow_insecure_sign_in_after_accept is true' do
129
+ original_option_value = Devise.allow_insecure_sign_in_after_accept
130
+ Devise.allow_insecure_sign_in_after_accept = true
131
+
110
132
  User.invite!(:email => "valid@email.com")
111
133
  set_password :invitation_token => Thread.current[:token]
134
+
112
135
  assert_equal root_path, current_path
136
+ Devise.allow_insecure_sign_in_after_accept = original_option_value
137
+ end
138
+
139
+ test 'does not sign in user automatically after setting it\'s password if config.allow_insecure_sign_in_after_accept is false' do
140
+ original_option_value = Devise.allow_insecure_sign_in_after_accept
141
+ Devise.allow_insecure_sign_in_after_accept = false
142
+
143
+ User.invite!(:email => "valid@email.com")
144
+ set_password :invitation_token => Thread.current[:token]
145
+
146
+ assert_equal new_user_session_path, current_path
147
+ Devise.allow_insecure_sign_in_after_accept = original_option_value
113
148
  end
114
149
 
115
150
  test 'clear token and set invitation_accepted_at after recover password instead of accept_invitation' do
@@ -181,6 +181,11 @@ class InvitableTest < ActiveSupport::TestCase
181
181
  assert_not_equal old_encrypted_password, user.encrypted_password
182
182
  end
183
183
 
184
+ test 'should not override password on invite!' do
185
+ user = User.invite!(:email => "valid@email.com", :password => 'password', :password_confirmation => 'password', :skip_invitation => true)
186
+ assert user.valid?
187
+ end
188
+
184
189
  test 'should clear invitation token and set invitation_accepted_at while accepting the password' do
185
190
  user = User.invite!(:email => "valid@email.com")
186
191
  assert user.invitation_token.present?
@@ -195,8 +200,10 @@ class InvitableTest < ActiveSupport::TestCase
195
200
  user = User.invite!(:email => "valid@email.com")
196
201
  assert user.invitation_token.present?
197
202
  assert_nil user.invitation_accepted_at
203
+ old_encrypted_password = user.encrypted_password
198
204
  User.accept_invitation!(:invitation_token => user.invitation_token, :password => '123456789', :password_confirmation => '987654321')
199
205
  user.reload
206
+ assert_equal old_encrypted_password, user.encrypted_password
200
207
  assert user.invitation_token.present?
201
208
  assert_nil user.invitation_accepted_at
202
209
  end
@@ -432,6 +439,21 @@ class InvitableTest < ActiveSupport::TestCase
432
439
  assert !user.valid_password?('new_password')
433
440
  end
434
441
 
442
+ test 'should check if created by invitation' do
443
+ user = User.invite!(:email => "valid@email.com")
444
+ assert user.created_by_invite?
445
+
446
+ invited_user = User.accept_invitation!(
447
+ :invitation_token => Thread.current[:token],
448
+ :password => 'new_password',
449
+ :password_confirmation => 'new_password',
450
+ :username => 'a'
451
+ )
452
+ user.reload
453
+ assert user.created_by_invite?
454
+ end
455
+
456
+
435
457
  test 'should set other attributes on accepting invitation' do
436
458
  user = new_user(:password => nil, :password_confirmation => nil)
437
459
  user.invite!
@@ -611,19 +633,23 @@ class InvitableTest < ActiveSupport::TestCase
611
633
  assert !user.errors.empty?
612
634
  end
613
635
 
614
- test "should count accepted and not accepted invitations" do
636
+ test "should count invited, created_by_invite, accepted and not accepted invitations" do
615
637
  assert_equal 0, User.invitation_not_accepted.count
616
638
  assert_equal 0, User.invitation_accepted.count
639
+ assert_equal 0, User.created_by_invite.count
617
640
 
618
641
  User.invite!(:email => "invalid@email.com")
642
+ User.invite!(:email => "another_invalid@email.com")
619
643
  user = User.invite!(:email => "valid@email.com")
620
644
 
621
- assert_equal 2, User.invitation_not_accepted.count
645
+ assert_equal 3, User.invitation_not_accepted.count
622
646
  assert_equal 0, User.invitation_accepted.count
647
+ assert_equal 3, User.created_by_invite.count
623
648
 
624
649
  user.accept_invitation!
625
- assert_equal 1, User.invitation_not_accepted.count
650
+ assert_equal 2, User.invitation_not_accepted.count
626
651
  assert_equal 1, User.invitation_accepted.count
652
+ assert_equal 3, User.created_by_invite.count
627
653
  end
628
654
 
629
655
  test "should preserve return values of Devise::Recoverable#reset_password!" do
@@ -124,11 +124,20 @@ Devise.setup do |config|
124
124
  # Default: nil
125
125
  # config.invited_by_class_name = 'User'
126
126
 
127
+ # The foreign key to the inviting model (if invited_by_class_name is set)
128
+ # Default: :invited_by_id
129
+ # config.invited_by_foreign_key = :invited_by_id
130
+
127
131
  # The column name used for counter_cache column. If this is nil,
128
132
  # the #invited_by association is declared without counter_cache.
129
133
  # Default: nil
130
134
  config.invited_by_counter_cache = :invitations_count
131
135
 
136
+ # Auto-login after the user accepts the invite. If this is false,
137
+ # the user will need to manually log in after accepting the invite.
138
+ # Default: true
139
+ # config.allow_insecure_sign_in_after_accept = false
140
+
132
141
  # ==> Configuration for :confirmable
133
142
  # A period that the user is allowed to access the website even without
134
143
  # confirming his account. For instance, if set to 2.days, the user will be
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: devise_invitable
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.4.1
4
+ version: 1.5.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sergio Cambra
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-02-17 00:00:00.000000000 Z
11
+ date: 2015-09-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler