devise_invitable 1.4.1 → 1.5.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 3baf83979bda300001727d86af188f93a91bf95c
4
- data.tar.gz: 74d6ac1f55c041e7075e4c1e8ec026e470fdfe12
3
+ metadata.gz: 9ebdddd2e1426fdde9ad700f7f60e4cedf3e48ab
4
+ data.tar.gz: 3a783c0d76c73a4a6af2c07cda15b80904fecdcf
5
5
  SHA512:
6
- metadata.gz: 0b08b0b990d88fd99aed53e4d3082db8354bda2e5046fad13901debde5ecb614ba5cdcea7e11709798ea16d4dc99dac27dbd622534179cf1d9a3cbaaaba38bda
7
- data.tar.gz: 3ca119718132addd0b7b331ac4fe81c4e21852b6480ef10c3ed742492b82e86e6fbd01f84957e5a7b6426aba2bbd5eaeb8f65460730399f3e286e125355ca77f
6
+ metadata.gz: b85b1eabee1011a5175f98ca92f05f39a1f49e3da2666f17bafa8a87d22115700d6f7beee89261a5096d70dcfeec473834dc43bfcaa5a967b63fc78b7789bb7f
7
+ data.tar.gz: 483b574690f8a3064b7d4f941a7fad4e321fe89d677192b82e68d0d0f3feba49d92125054f17e89521c2810e9feb80de12ee135895728d7d1f633bdd845845ea
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
 
@@ -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
 
@@ -43,10 +43,15 @@ class Devise::InvitationsController < DeviseController
43
43
  yield resource if block_given?
44
44
 
45
45
  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)
46
+ if Devise.allow_insecure_sign_in_after_accept
47
+ flash_message = resource.active_for_authentication? ? :updated : :updated_not_active
48
+ set_flash_message :notice, flash_message if is_flashing_format?
49
+ sign_in(resource_name, resource)
50
+ respond_with resource, :location => after_accept_path_for(resource)
51
+ else
52
+ set_flash_message :notice, :updated_not_active if is_flashing_format?
53
+ respond_with resource, :location => new_session_path(resource_name)
54
+ end
50
55
  else
51
56
  respond_with_navigational(resource){ render :edit }
52
57
  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,16 +142,16 @@ 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
153
  def after_password_reset
146
- super
154
+ super if defined?(super)
147
155
  accept_invitation! if invited_to_sign_up?
148
156
  end
149
157
 
@@ -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
@@ -303,6 +323,8 @@ module Devise
303
323
  Devise::Models.config(self, :invitation_limit)
304
324
  Devise::Models.config(self, :invite_key)
305
325
  Devise::Models.config(self, :resend_invitation)
326
+ Devise::Models.config(self, :allow_insecure_sign_in_after_accept)
327
+
306
328
  end
307
329
  end
308
330
  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.0'
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| -%>
@@ -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.0
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-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler