devise_invitable 1.4.1 → 1.5.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 3baf83979bda300001727d86af188f93a91bf95c
4
- data.tar.gz: 74d6ac1f55c041e7075e4c1e8ec026e470fdfe12
3
+ metadata.gz: 09f7285fbf0dcc7da0b16e9a26bbc9c385e19720
4
+ data.tar.gz: 0b862b791cd58df3fc8ad6eba813c76534e89cba
5
5
  SHA512:
6
- metadata.gz: 0b08b0b990d88fd99aed53e4d3082db8354bda2e5046fad13901debde5ecb614ba5cdcea7e11709798ea16d4dc99dac27dbd622534179cf1d9a3cbaaaba38bda
7
- data.tar.gz: 3ca119718132addd0b7b331ac4fe81c4e21852b6480ef10c3ed742492b82e86e6fbd01f84957e5a7b6426aba2bbd5eaeb8f65460730399f3e286e125355ca77f
6
+ metadata.gz: 8bf23dd018217299609a033f0ea3666a4f0358580a6b2a14bff4405666ae82217de809b1fdac77a12269349337d8756a601c635e427962fa343c81d4bdf0c765
7
+ data.tar.gz: 560b395e0616d53b6b2d3164cd9ccd34a017517256f627a098c4971b4b85a55f41f61810f735dc2c51d9afcfb1acf848574bf1b32e2185b8ec47b9afe47d946c
data/CHANGELOG CHANGED
@@ -1,3 +1,5 @@
1
+ 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
2
+
1
3
  = 1.4.0
2
4
  Override active_for_authentication? and inactive_message instead of valid_password?
3
5
  To use counter_cache, invited_by_counter_cache must be set, no more checking of invitations_count to enable counter cache
data/README.rdoc CHANGED
@@ -1,5 +1,5 @@
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"/>}[http://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
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.
5
5
 
@@ -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,8 @@ 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
+ * allow_insecure_sign_in_after_accept: automatically sign in the user after they set a password. Enabled by default.
136
+
135
137
  For more details, see <tt>config/initializers/devise.rb</tt> (after you invoked the "devise_invitable:install" generator described above).
136
138
 
137
139
  == Configuring views
@@ -254,9 +256,11 @@ You can add :skip_invitation to attributes hash if skip_invitation is added to a
254
256
  User.invite!(:email => "new_user@example.com", :name => "John Doe", :skip_invitation => true)
255
257
  # => the record will be created, but the invitation email will not be sent
256
258
 
257
- Skip_invitation skips sending the email, but sets invitation_token, so invited_to_sign_up? on the
259
+ Skip_invitation skips sending the email, but sets invitation_token, so <tt>invited_to_sign_up?</tt> on the
258
260
  resulting user returns true.
259
261
 
262
+ To check if a perticular user is created by invitation, irrespective to state of invitation one can use <tt>created_by_invite?</tt>
263
+
260
264
  **Warning**
261
265
 
262
266
  When using skip_invitation you must send the email with the user object instance that generated the tokens, as
@@ -295,8 +299,9 @@ The callbacks support all options and arguments available to the standard callba
295
299
 
296
300
  A pair of scopes to find those users that have accepted, and those that have not accepted, invitations are defined:
297
301
 
298
- User.invitation_accepted # => returns all Users for whom the invitation_accepted_at attribute is not nil
302
+ User.invitation_accepted # => returns all Users for whom the invitation_accepted_at attribute is not nil
299
303
  User.invitation_not_accepted # => returns all Users for whom the invitation_accepted_at attribute is nil
304
+ User.created_by_invite # => returns all Users who are created by invitations, irrespective to invitation status
300
305
 
301
306
  == Integration in a Rails application
302
307
 
@@ -355,6 +360,7 @@ DeviseInvitable uses flash messages with I18n with the flash keys <tt>:send_inst
355
360
  send_instructions: 'An invitation email has been sent to %{email}.'
356
361
  invitation_token_invalid: 'The invitation token provided is not valid!'
357
362
  updated: 'Your password was set successfully. You are now signed in.'
363
+ updated_not_active: 'Your password was set successfully.'
358
364
 
359
365
  You can also create distinct messages based on the resource you've configured using the singular name given in routes:
360
366
 
@@ -365,6 +371,7 @@ You can also create distinct messages based on the resource you've configured us
365
371
  send_instructions: 'A new user invitation has been sent to %{email}.'
366
372
  invitation_token_invalid: 'Your invitation token is not valid!'
367
373
  updated: 'Welcome on board! You are now signed in.'
374
+ updated_not_active: 'Welcome on board! Sign in to continue.'
368
375
 
369
376
  The DeviseInvitable mailer uses the same pattern as Devise to create mail subject messages:
370
377
 
@@ -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
@@ -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:
@@ -47,9 +47,11 @@ module Devise
47
47
 
48
48
  scope :no_active_invitation, lambda { where(:invitation_token => nil) }
49
49
  if defined?(Mongoid) && defined?(Mongoid::Document) && self < Mongoid::Document
50
+ scope :created_by_invite, lambda { where(:invitation_sent_at.ne => nil) }
50
51
  scope :invitation_not_accepted, lambda { where(:invitation_accepted_at => nil, :invitation_token.ne => nil) }
51
52
  scope :invitation_accepted, lambda { where(:invitation_accepted_at.ne => nil) }
52
53
  else
54
+ scope :created_by_invite, lambda { where(arel_table[:invitation_sent_at].not_eq(nil)) }
53
55
  scope :invitation_not_accepted, lambda { where(arel_table[:invitation_token].not_eq(nil)).where(:invitation_accepted_at => nil) }
54
56
  scope :invitation_accepted, lambda { where(arel_table[:invitation_accepted_at].not_eq(nil)) }
55
57
 
@@ -85,6 +87,11 @@ module Devise
85
87
  end
86
88
  end
87
89
 
90
+ # Verify wheather a user is created by invitation, irrespective to invitation status
91
+ def created_by_invite?
92
+ invitation_sent_at.present?
93
+ end
94
+
88
95
  # Verifies whether a user has been invited or not
89
96
  def invited_to_sign_up?
90
97
  persisted? && invitation_token.present?
@@ -102,23 +109,24 @@ module Devise
102
109
 
103
110
  # Reset invitation token and send invitation again
104
111
  def invite!(invited_by = nil)
112
+ # This is an order-dependant assignment, this can't be moved
105
113
  was_invited = invited_to_sign_up?
106
114
 
107
115
  # Required to workaround confirmable model's confirmation_required? method
108
116
  # being implemented to check for non-nil value of confirmed_at
109
- if self.new_record? && self.respond_to?(:confirmation_required?, true)
117
+ if new_record_and_responds_to?(:confirmation_required?)
110
118
  def self.confirmation_required?; false; end
111
119
  end
112
120
 
113
121
  yield self if block_given?
114
- generate_invitation_token if self.invitation_token.nil? || (!skip_invitation || @raw_invitation_token.nil?)
122
+ generate_invitation_token if no_token_present_or_skip_invitation?
115
123
  self.invitation_created_at = Time.now.utc
116
124
  self.invitation_sent_at = self.invitation_created_at unless skip_invitation
117
125
  self.invited_by = invited_by if invited_by
118
126
 
119
127
  # 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)
128
+ self.downcase_keys if new_record_and_responds_to?(:downcase_keys)
129
+ self.strip_whitespace if new_record_and_responds_to?(:strip_whitespace)
122
130
 
123
131
  if save(:validate => false)
124
132
  self.invited_by.decrement_invitation_limit! if !was_invited and self.invited_by.present?
@@ -134,15 +142,15 @@ module Devise
134
142
  end
135
143
 
136
144
  # Only verify password when is not invited
137
- def active_for_authentication?
138
- super unless invited_to_sign_up?
145
+ def valid_password?(password)
146
+ super unless block_from_invitation?
139
147
  end
140
148
 
141
- def inactive_message
142
- invited_to_sign_up? ? :invited : super
149
+ def unauthenticated_message
150
+ block_from_invitation? ? :invited : super
143
151
  end
144
152
 
145
- def after_password_reset
153
+ def clear_reset_password_token
146
154
  super
147
155
  accept_invitation! if invited_to_sign_up?
148
156
  end
@@ -179,6 +187,10 @@ module Devise
179
187
  @skip_password ||= false
180
188
  end
181
189
 
190
+ def block_from_invitation?
191
+ invited_to_sign_up?
192
+ end
193
+
182
194
  # Checks if the invitation for the user is within the limit time.
183
195
  # We do this by calculating if the difference between today and the
184
196
  # invitation sent date does not exceed the invite for time configured.
@@ -215,6 +227,14 @@ module Devise
215
227
  generate_invitation_token && save(:validate => false)
216
228
  end
217
229
 
230
+ def new_record_and_responds_to?(method)
231
+ self.new_record? && self.respond_to?(method, true)
232
+ end
233
+
234
+ def no_token_present_or_skip_invitation?
235
+ self.invitation_token.nil? || (!skip_invitation || @raw_invitation_token.nil?)
236
+ end
237
+
218
238
  module ClassMethods
219
239
  # Return fields to invite
220
240
  def invite_key_fields
@@ -284,7 +304,6 @@ module Devise
284
304
 
285
305
  invitable = find_or_initialize_with_error_by(:invitation_token, invitation_token)
286
306
  invitable.errors.add(:invitation_token, :invalid) if invitable.invitation_token && invitable.persisted? && !invitable.valid_invitation?
287
- invitable.invitation_token = original_token
288
307
  invitable unless only_valid && invitable.errors.present?
289
308
  end
290
309
 
@@ -303,6 +322,8 @@ module Devise
303
322
  Devise::Models.config(self, :invitation_limit)
304
323
  Devise::Models.config(self, :invite_key)
305
324
  Devise::Models.config(self, :resend_invitation)
325
+ Devise::Models.config(self, :allow_insecure_sign_in_after_accept)
326
+
306
327
  end
307
328
  end
308
329
  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.1'
3
3
  end
@@ -66,6 +66,11 @@ module Devise
66
66
  # the #invited_by association is declared without counter_cache. (default: nil)
67
67
  mattr_accessor :invited_by_counter_cache
68
68
  @@invited_by_counter_cache = nil
69
+
70
+ # Public: Auto-login after the user accepts the invite. If this is false,
71
+ # the user will need to manually log in after accepting the invite (default: false).
72
+ mattr_accessor :allow_insecure_sign_in_after_accept
73
+ @@allow_insecure_sign_in_after_accept = true
69
74
  end
70
75
 
71
76
  Devise.add_module :invitable, :controller => :invitations, :model => 'devise_invitable/model', :route => {:invitation => [nil, :new, :accept]}
@@ -6,10 +6,10 @@ 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
15
 
@@ -22,7 +22,7 @@ class DeviseInvitableAddTo<%= table_name.camelize %> < ActiveRecord::Migration
22
22
 
23
23
  def down
24
24
  change_table :<%= table_name %> do |t|
25
- t.remove_references :invited_by, :polymorphic => true
25
+ t.remove_references :invited_by, polymorphic: true
26
26
  t.remove :invitations_count, :invitation_limit, :invitation_sent_at, :invitation_accepted_at, :invitation_token, :invitation_created_at
27
27
  end
28
28
  change_column_null :<%= table_name %>, :encrypted_password, false
@@ -57,6 +57,11 @@ module DeviseInvitable
57
57
  # Default: nil
58
58
  # config.invited_by_counter_cache = :invitations_count
59
59
 
60
+ # Auto-login after the user accepts the invite. If this is false,
61
+ # the user will need to manually log in after accepting the invite.
62
+ # Default: false
63
+ # config.allow_insecure_sign_in_after_accept = true
64
+
60
65
  CONTENT
61
66
  end
62
67
  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| -%>
@@ -26,6 +26,9 @@ class DeviseInvitable::RegistrationsControllerTest < ActionController::TestCase
26
26
 
27
27
  @invitee = User.where(:email => invitee_email).first
28
28
  assert @invitee.encrypted_password.blank?, "the password should be unset"
29
+ assert_nil @invitee.invitation_accepted_at
30
+ assert_not_nil @invitee.invitation_token
31
+ assert !@invitee.confirmed?
29
32
 
30
33
  # sign_up the invitee
31
34
  assert_difference('ActionMailer::Base.deliveries.size') do
@@ -47,8 +50,8 @@ class DeviseInvitable::RegistrationsControllerTest < ActionController::TestCase
47
50
  assert_nil @invitee.invitation_token
48
51
  assert @invitee.invited_by_id.present?
49
52
  assert @invitee.invited_by_type.present?
50
- assert !@invitee.confirmed?
51
53
  assert @invitee.confirmation_token.present?
54
+ assert !@invitee.confirmed?
52
55
  end
53
56
 
54
57
  test "not invitable resources can register" 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!
@@ -106,10 +125,26 @@ class InvitationTest < ActionDispatch::IntegrationTest
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
@@ -431,6 +431,21 @@ class InvitableTest < ActiveSupport::TestCase
431
431
  user.reload
432
432
  assert !user.valid_password?('new_password')
433
433
  end
434
+
435
+ test 'should check if created by invitation' do
436
+ user = User.invite!(:email => "valid@email.com")
437
+ assert user.created_by_invite?
438
+
439
+ invited_user = User.accept_invitation!(
440
+ :invitation_token => Thread.current[:token],
441
+ :password => 'new_password',
442
+ :password_confirmation => 'new_password',
443
+ :username => 'a'
444
+ )
445
+ user.reload
446
+ assert user.created_by_invite?
447
+ end
448
+
434
449
 
435
450
  test 'should set other attributes on accepting invitation' do
436
451
  user = new_user(:password => nil, :password_confirmation => nil)
@@ -611,19 +626,23 @@ class InvitableTest < ActiveSupport::TestCase
611
626
  assert !user.errors.empty?
612
627
  end
613
628
 
614
- test "should count accepted and not accepted invitations" do
629
+ test "should count invited, created_by_invite, accepted and not accepted invitations" do
615
630
  assert_equal 0, User.invitation_not_accepted.count
616
631
  assert_equal 0, User.invitation_accepted.count
632
+ assert_equal 0, User.created_by_invite.count
617
633
 
618
634
  User.invite!(:email => "invalid@email.com")
635
+ User.invite!(:email => "another_invalid@email.com")
619
636
  user = User.invite!(:email => "valid@email.com")
620
637
 
621
- assert_equal 2, User.invitation_not_accepted.count
638
+ assert_equal 3, User.invitation_not_accepted.count
622
639
  assert_equal 0, User.invitation_accepted.count
640
+ assert_equal 3, User.created_by_invite.count
623
641
 
624
642
  user.accept_invitation!
625
- assert_equal 1, User.invitation_not_accepted.count
643
+ assert_equal 2, User.invitation_not_accepted.count
626
644
  assert_equal 1, User.invitation_accepted.count
645
+ assert_equal 3, User.created_by_invite.count
627
646
  end
628
647
 
629
648
  test "should preserve return values of Devise::Recoverable#reset_password!" do
@@ -129,6 +129,11 @@ Devise.setup do |config|
129
129
  # Default: nil
130
130
  config.invited_by_counter_cache = :invitations_count
131
131
 
132
+ # Auto-login after the user accepts the invite. If this is false,
133
+ # the user will need to manually log in after accepting the invite.
134
+ # Default: true
135
+ # config.allow_insecure_sign_in_after_accept = false
136
+
132
137
  # ==> Configuration for :confirmable
133
138
  # A period that the user is allowed to access the website even without
134
139
  # 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.1
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-06-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler