devise_invitable 1.2.1 → 1.3.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA512:
3
+ metadata.gz: f63e9220602bf9934658384637404fdf89b2dfcd4f7b2b7ed77c31cddfbab70e6ca7fa89a2fa2dccf489db7b2e80ff6634cfadf320ee366769b42000da4e9ac5
4
+ data.tar.gz: d54dfb69ad95bf58f8a69c5c358ba64e9cfdf569536682f6e8614cc2f10550984657cb74353ab64a9fd59e1fed16991b9e8e4e5d120ec3583ad01f36b961bd1c
5
+ SHA1:
6
+ metadata.gz: 861482f2cb8845f6335441e94c3da04912511e1b
7
+ data.tar.gz: 54799f3b072ffc7dcd3080d5f394fa2c62e64990
data/CHANGELOG ADDED
@@ -0,0 +1,5 @@
1
+ = 1.3.0
2
+ Now devise 3.1 compatible, @token must be used instead of @resource.invitation_token in mail views
3
+
4
+ = 1.2.0
5
+ 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
@@ -3,11 +3,9 @@
3
3
 
4
4
  It adds support to devise[http://github.com/plataformatec/devise] for send invitations by email (it requires to be authenticated) and accept the invitation setting the password.
5
5
 
6
- DeviseInvitable currently only support Rails 3, 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}[http://rubygems.org/gems/devise_invitable/versions/0.2.3]
7
7
 
8
- If you want to use Rails 4 or devise 3, you must use git:
9
-
10
- gem 'devise_invitable', :github => 'scambra/devise_invitable'
8
+ If you want to use devise 3.0.x, you must use 1.2.1, newer versions require devise >= 3.1.0
11
9
 
12
10
  == Installation
13
11
 
@@ -49,7 +47,7 @@ Add t.invitable to your Devise model migration:
49
47
  create_table :users do
50
48
  ...
51
49
  ## Invitable
52
- t.string :invitation_token, :limit => 60
50
+ t.string :invitation_token
53
51
  t.datetime :invitation_created_at
54
52
  t.datetime :invitation_sent_at
55
53
  t.datetime :invitation_accepted_at
@@ -62,15 +60,15 @@ Add t.invitable to your Devise model migration:
62
60
 
63
61
  or for a model that already exists, define a migration to add DeviseInvitable to your model:
64
62
 
65
- change_table :users do |t|
66
- t.string :invitation_token, :limit => 60
67
- t.datetime :invitation_created_at
68
- t.datetime :invitation_sent_at
69
- t.datetime :invitation_accepted_at
70
- t.integer :invitation_limit
71
- t.integer :invited_by_id
72
- t.string :invited_by_type
73
-
63
+ def change
64
+ add_column :users, :invitation_token, :string
65
+ add_column :users, :invitation_created_at, :datetime
66
+ add_column :users, :invitation_sent_at, :datetime
67
+ add_column :users, :invitation_accepted_at, :datetime
68
+ add_column :users, :invitation_limit, :integer
69
+ add_column :users, :invited_by_id, :integer
70
+ add_column :users, :invited_by_type, :string
71
+ add_index :users, :invitation_token, :unique => true
74
72
  end
75
73
 
76
74
  # Allow null encrypted_password
@@ -78,6 +76,16 @@ or for a model that already exists, define a migration to add DeviseInvitable to
78
76
  # Allow null password_salt (add it if you are using Devise's encryptable module)
79
77
  change_column :users, :password_salt, :string, :null => true
80
78
 
79
+ If you previously used devise_invitable with a :limit on :invitation_token, remove it:
80
+
81
+ def up
82
+ change_column :users, :invitation_token, :string, :limit => nil
83
+ end
84
+
85
+ def down
86
+ change_column :users, :invitation_token, :string, :limit => 60
87
+ end
88
+
81
89
  == Mongoid Field Definitions
82
90
  If you are using Mongoid, define the following fields and indexes within your invitable model:
83
91
 
@@ -116,7 +124,7 @@ or directly as parameters to the <tt>devise</tt> method:
116
124
 
117
125
  * invitation_limit: The number of invitations users can send. The default value of nil means users can send as many invites as they want, there is no limit for any user, invitation_limit column is not used. A setting of 0 means they can't send invitations. A setting n > 0 means they can send n invitations. You can change invitation_limit column for some users so they can send more or less invitations, even with global invitation_limit = 0.
118
126
 
119
- * invite_key: The key to be used to check existing users when sending an invitation. You can use multiple keys. This value must be a hash with the invite key as hash keys, and regexp to validate format as values. If you don't want to validate the key you can set nil as validation format. The default value is looking for users by email and validating with Devise.email_regexp {:email => Devise.email_regexp}.
127
+ * invite_key: The key to be used to check existing users when sending an invitation. You can use multiple keys. This value must be a hash with the invite key as hash keys, and values that respond to the === operator(includes procs and regexes). The default value is looking for users by email and validating with Devise.email_regexp {:email => Devise.email_regexp}.
120
128
 
121
129
  * validate_on_invite: force a record to be valid before being actually invited.
122
130
 
@@ -164,6 +172,32 @@ be sure that you generate the views and put them into the controller that you ge
164
172
 
165
173
  rails generate devise_invitable:views users/invitations
166
174
 
175
+ == Strong Parameters
176
+
177
+ 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]
178
+
179
+ 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:
180
+
181
+ * invite (Devise::InvitationsController#create) - Permits only the authentication keys (like email)
182
+ * accept_invitation (Devise::InvitationsController#update) - Permits invitation_token plus password and password_confirmation
183
+
184
+ Here is an example of what your application controller might need to include in order to add these parameters to the invitation view:
185
+
186
+ before_filter :configure_permitted_parameters, if: :devise_controller?
187
+
188
+ protected
189
+
190
+ def configure_permitted_parameters
191
+ # Only add some parameters
192
+ devise_parameter_sanitizer.for(:accept_invitation).concat [:first_name, :last_name, :phone]
193
+ # Override accepted parameters
194
+ devise_parameter_sanitizer.for(:accept_invitation) do |u|
195
+ u.permit(:first_name, :last_name, :phone, :password, :password_confirmation,
196
+ :invitation_token)
197
+ end
198
+ end
199
+
200
+
167
201
  == Usage
168
202
 
169
203
  === Send an invitation
@@ -14,7 +14,7 @@ class Devise::InvitationsController < DeviseController
14
14
 
15
15
  # POST /resource/invitation
16
16
  def create
17
- self.resource = resource_class.invite!(invite_params, current_inviter)
17
+ self.resource = invite_resource
18
18
 
19
19
  if resource.errors.empty?
20
20
  set_flash_message :notice, :send_instructions, :email => self.resource.email if self.resource.invitation_sent_at
@@ -26,6 +26,7 @@ class Devise::InvitationsController < DeviseController
26
26
 
27
27
  # GET /resource/invitation/accept?invitation_token=abcdef
28
28
  def edit
29
+ resource.invitation_token = params[:invitation_token]
29
30
  render :edit
30
31
  end
31
32
 
@@ -51,6 +52,11 @@ class Devise::InvitationsController < DeviseController
51
52
  end
52
53
 
53
54
  protected
55
+
56
+ def invite_resource
57
+ resource_class.invite!(invite_params, current_inviter)
58
+ end
59
+
54
60
  def current_inviter
55
61
  @current_inviter ||= authenticate_inviter!
56
62
  end
@@ -64,18 +70,18 @@ class Devise::InvitationsController < DeviseController
64
70
  end
65
71
 
66
72
  def resource_from_invitation_token
67
- unless params[:invitation_token] && self.resource = resource_class.to_adapter.find_first(params.slice(:invitation_token))
73
+ unless params[:invitation_token] && self.resource = resource_class.find_by_invitation_token(params[:invitation_token], true)
68
74
  set_flash_message(:alert, :invitation_token_invalid)
69
75
  redirect_to after_sign_out_path_for(resource_name)
70
76
  end
71
77
  end
72
78
 
73
79
  def invite_params
74
- devise_parameter_sanitizer.for(:invite)
80
+ devise_parameter_sanitizer.sanitize(:invite)
75
81
  end
76
82
 
77
83
  def update_resource_params
78
- devise_parameter_sanitizer.for(:accept_invitation)
84
+ devise_parameter_sanitizer.sanitize(:accept_invitation)
79
85
  end
80
86
 
81
87
  end
@@ -2,7 +2,7 @@
2
2
 
3
3
  <p>Someone has invited you to <%= root_url %>, you can accept it through the link below.</p>
4
4
 
5
- <p><%= link_to 'Accept invitation', accept_invitation_url(@resource, :invitation_token => @resource.invitation_token) %></p>
5
+ <p><%= link_to 'Accept invitation', accept_invitation_url(@resource, :invitation_token => @token) %></p>
6
6
 
7
7
  <p>If you don't want to accept the invitation, please ignore this email.<br />
8
8
  Your account won't be created until you access the link above and set your password.</p>
@@ -4,13 +4,9 @@ module DeviseInvitable
4
4
  module Mailer
5
5
 
6
6
  # Deliver an invitation email
7
- def invitation_instructions(record, opts={})
8
- # optional arguments introduced in Devise 2.2.0, remove check once support for < 2.2.0 is dropped.
9
- if Gem::Version.new(Devise::VERSION.dup) < Gem::Version.new('2.2.0')
10
- devise_mail(record, :invitation_instructions)
11
- else
12
- devise_mail(record, :invitation_instructions, opts)
13
- end
7
+ def invitation_instructions(record, token, opts={})
8
+ @token = token
9
+ devise_mail(record, :invitation_instructions, opts)
14
10
  end
15
11
  end
16
12
  end
@@ -25,6 +25,7 @@ module Devise
25
25
 
26
26
  attr_accessor :skip_invitation
27
27
  attr_accessor :completing_invite
28
+ attr_reader :raw_invitation_token
28
29
 
29
30
  included do
30
31
  include ::DeviseInvitable::Inviter
@@ -64,14 +65,6 @@ module Devise
64
65
  fields
65
66
  end
66
67
 
67
- def invitation_fields
68
- fields = [:invitation_created_at, :invitation_sent_at, :invited_by_id, :invited_by_type]
69
- if Devise.invited_by_class_name
70
- fields -= [:invited_by_type]
71
- end
72
- fields
73
- end
74
-
75
68
  # Accept an invitation by clearing invitation token and and setting invitation_accepted_at
76
69
  def accept_invitation
77
70
  self.invitation_accepted_at = Time.now.utc
@@ -111,11 +104,6 @@ module Devise
111
104
  invitation_accepted? || !invited_to_sign_up?
112
105
  end
113
106
 
114
- def invited?
115
- ActiveSupport::Deprecation.warn "invited? is deprecated and will be removed from DeviseInvitable 1.1.0 (use invited_to_sign_up? instead)"
116
- invited_to_sign_up?
117
- end
118
-
119
107
  # Reset invitation token and send invitation again
120
108
  def invite!(invited_by = nil)
121
109
  was_invited = invited_to_sign_up?
@@ -126,7 +114,7 @@ module Devise
126
114
  def self.confirmation_required?; false; end
127
115
  end
128
116
 
129
- generate_invitation_token if self.invitation_token.nil?
117
+ generate_invitation_token if self.invitation_token.nil? || (!@skip_invitation && @raw_invitation_token.nil?)
130
118
  self.invitation_created_at = Time.now.utc
131
119
  self.invitation_sent_at = self.invitation_created_at unless @skip_invitation
132
120
  self.invited_by = invited_by if invited_by
@@ -158,13 +146,24 @@ module Devise
158
146
  accept_invitation! if invited_to_sign_up?
159
147
  end
160
148
 
161
- def invite_key_valid?
162
- return true unless self.class.invite_key.is_a? Hash # FIXME: remove this line when deprecation is removed
163
- self.class.invite_key.all? do |key, regexp|
164
- regexp.nil? || self.send(key).try(:match, regexp)
149
+ def clear_errors_on_valid_keys
150
+ self.class.invite_key.each do |key, value|
151
+ self.errors.delete(key) if value === self.send(key)
165
152
  end
166
153
  end
167
154
 
155
+ # Deliver the invitation email
156
+ def deliver_invitation
157
+ generate_invitation_token! unless @raw_invitation_token
158
+ self.update_attribute :invitation_sent_at, Time.now.utc unless self.invitation_sent_at
159
+ send_devise_notification(:invitation_instructions, @raw_invitation_token)
160
+ end
161
+
162
+ # provide alias to the encrypted invitation_token stored by devise
163
+ def encrypted_invitation_token
164
+ self.invitation_token
165
+ end
166
+
168
167
  protected
169
168
  # Overriding the method in Devise's :validatable module so password is not required on inviting
170
169
  def password_required?
@@ -175,12 +174,6 @@ module Devise
175
174
  respond_to?(:confirmation_required?, true) && confirmation_required? && invitation_accepted?
176
175
  end
177
176
 
178
- # Deliver the invitation email
179
- def deliver_invitation
180
- self.update_attribute :invitation_sent_at, Time.now.utc unless self.invitation_sent_at
181
- send_devise_notification(:invitation_instructions)
182
- end
183
-
184
177
  # Checks if the invitation for the user is within the limit time.
185
178
  # We do this by calculating if the difference between today and the
186
179
  # invitation sent date does not exceed the invite for time configured.
@@ -208,18 +201,19 @@ module Devise
208
201
  # Generates a new random token for invitation, and stores the time
209
202
  # this token is being generated
210
203
  def generate_invitation_token
211
- self.invitation_token = self.class.invitation_token
204
+ raw, enc = Devise.token_generator.generate(self.class, :invitation_token)
205
+ @raw_invitation_token = raw
206
+ self.invitation_token = enc
207
+ end
208
+
209
+ def generate_invitation_token!
210
+ generate_invitation_token && save(:validate => false)
212
211
  end
213
212
 
214
213
  module ClassMethods
215
214
  # Return fields to invite
216
215
  def invite_key_fields
217
- if invite_key.is_a? Hash
218
- invite_key.keys
219
- else
220
- ActiveSupport::Deprecation.warn("invite_key should be a hash like {#{invite_key.inspect} => /.../}")
221
- Array(invite_key)
222
- end
216
+ invite_key.keys
223
217
  end
224
218
 
225
219
  # Attempt to find a user by its email. If a record is not found,
@@ -233,7 +227,9 @@ module Devise
233
227
  invite_key_array = invite_key_fields
234
228
  attributes_hash = {}
235
229
  invite_key_array.each do |k,v|
236
- attributes_hash[k] = attributes.delete(k).to_s.strip
230
+ attribute = attributes.delete(k)
231
+ attribute = attribute.to_s.strip if strip_whitespace_keys.include?(k)
232
+ attributes_hash[k] = attribute
237
233
  end
238
234
 
239
235
  invitable = find_or_initialize_with_errors(invite_key_array, attributes_hash)
@@ -243,22 +239,20 @@ module Devise
243
239
  invitable.skip_password = true
244
240
  invitable.valid? if self.validate_on_invite
245
241
  if invitable.new_record?
246
- invitable.errors.clear if !self.validate_on_invite and invitable.invite_key_valid?
242
+ invitable.clear_errors_on_valid_keys if !self.validate_on_invite
247
243
  elsif !invitable.invited_to_sign_up? || !self.resend_invitation
248
244
  invite_key_array.each do |key|
249
245
  invitable.errors.add(key, :taken)
250
246
  end
251
247
  end
252
248
 
253
- if invitable.errors.empty?
254
- yield invitable if block_given?
255
- mail = invitable.invite!
256
- end
249
+ yield invitable if block_given?
250
+ mail = invitable.invite! if invitable.errors.empty?
257
251
  [invitable, mail]
258
252
  end
259
253
 
260
254
  def invite!(attributes={}, invited_by=nil, &block)
261
- invitable, mail = _invite(attributes, invited_by, &block)
255
+ invitable, mail = _invite(attributes.with_indifferent_access, invited_by, &block)
262
256
  invitable
263
257
  end
264
258
 
@@ -273,8 +267,8 @@ module Devise
273
267
  # error in invitation_token attribute.
274
268
  # Attributes must contain invitation_token, password and confirmation
275
269
  def accept_invitation!(attributes={})
276
- invitable = find_or_initialize_with_error_by(:invitation_token, attributes.delete(:invitation_token))
277
- invitable.errors.add(:invitation_token, :invalid) if invitable.invitation_token && invitable.persisted? && !invitable.valid_invitation?
270
+ original_token = attributes.delete(:invitation_token)
271
+ invitable = find_by_invitation_token(original_token, false)
278
272
  if invitable.errors.empty?
279
273
  invitable.assign_attributes(attributes)
280
274
  invitable.accept_invitation!
@@ -282,6 +276,18 @@ module Devise
282
276
  invitable
283
277
  end
284
278
 
279
+ def find_by_invitation_token(original_token, only_valid)
280
+ invitation_token = Devise.token_generator.digest(self, :invitation_token, original_token)
281
+
282
+ invitable = find_or_initialize_with_error_by(:invitation_token, invitation_token)
283
+ if !invitable.persisted? && Devise.allow_insecure_token_lookup
284
+ invitable = find_or_initialize_with_error_by(:invitation_token, original_token)
285
+ end
286
+ invitable.errors.add(:invitation_token, :invalid) if invitable.invitation_token && invitable.persisted? && !invitable.valid_invitation?
287
+ invitable.invitation_token = original_token
288
+ invitable unless only_valid && invitable.errors.present?
289
+ end
290
+
285
291
  # Generate a token checking if one does not already exist in the database.
286
292
  def invitation_token
287
293
  generate_token(:invitation_token)
@@ -1,11 +1,26 @@
1
1
  module DeviseInvitable
2
2
  module ParameterSanitizer
3
3
  def invite
4
- default_params.permit(resource_class.invite_key_fields)
4
+ default_params.permit self.for(:invite)
5
5
  end
6
6
 
7
7
  def accept_invitation
8
- default_params.permit([:password, :password_confirmation, :invitation_token])
8
+ default_params.permit self.for(:accept_invitation)
9
+ end
10
+
11
+ def self.included(base)
12
+ base.alias_method_chain :attributes_for, :invitable
13
+ end
14
+
15
+ private
16
+ def attributes_for_with_invitable(kind)
17
+ case kind
18
+ when :invite
19
+ resource_class.invite_key_fields
20
+ when :accept_invitation
21
+ [:password, :password_confirmation, :invitation_token]
22
+ else attributes_for_without_invitable(kind)
23
+ end
9
24
  end
10
25
  end
11
26
  end
@@ -1,3 +1,3 @@
1
1
  module DeviseInvitable
2
- VERSION = '1.2.1'
2
+ VERSION = '1.3.1'
3
3
  end
@@ -21,7 +21,7 @@ class DeviseInvitableAddTo<%= table_name.camelize %> < ActiveRecord::Migration
21
21
  def down
22
22
  change_table :<%= table_name %> do |t|
23
23
  t.remove_references :invited_by, :polymorphic => true
24
- t.remove :invitation_limit, :invitation_sent_at, :invitation_accepted_at, :invitation_token
24
+ t.remove :invitation_limit, :invitation_sent_at, :invitation_accepted_at, :invitation_token, :invitation_created_at
25
25
  end
26
26
  end
27
27
  end
@@ -1,29 +1,29 @@
1
1
  require 'test_helper'
2
2
  require 'integration_tests_helper'
3
3
 
4
- class InvitationTest < ActionDispatch::IntegrationTest
4
+ class InvitationRemoveTest < ActionDispatch::IntegrationTest
5
5
 
6
6
  test 'invited user can choose to remove his account/invite' do
7
7
  user = User.invite!(:email => "valid@email.com")
8
8
 
9
9
  # remove!
10
- visit remove_user_invitation_path(:invitation_token => user.invitation_token)
10
+ visit remove_user_invitation_path(:invitation_token => Thread.current[:token])
11
11
  assert_equal root_path, current_path
12
12
  assert page.has_css?('p#notice', :text => 'Your invitation was removed.')
13
13
 
14
14
  # try to remove again!
15
- visit remove_user_invitation_path(:invitation_token => user.invitation_token)
15
+ visit remove_user_invitation_path(:invitation_token => Thread.current[:token])
16
16
  assert_equal root_path, current_path
17
17
  assert page.has_css?('p#alert', :text => 'The invitation token provided is not valid!')
18
18
  end
19
19
 
20
20
  test 'accepted user cannot remove his account (by using the original invitation token)' do
21
21
  user = User.invite!(:email => "valid@email.com")
22
- saved_token = user.invitation_token
22
+ saved_token = Thread.current[:token]
23
23
  user.accept_invitation!
24
24
 
25
25
  visit remove_user_invitation_path(:invitation_token => saved_token)
26
26
  assert_equal root_path, current_path
27
27
  assert page.has_css?('p#alert', :text => 'The invitation token provided is not valid!')
28
28
  end
29
- end
29
+ end
@@ -75,7 +75,7 @@ class InvitationTest < ActionDispatch::IntegrationTest
75
75
 
76
76
  test 'not authenticated user with valid invitation token but invalid password should not be able to set his password' do
77
77
  user = User.invite!(:email => "valid@email.com")
78
- set_password :invitation_token => user.invitation_token do
78
+ set_password :invitation_token => Thread.current[:token] do
79
79
  fill_in 'Password confirmation', :with => 'other_password'
80
80
  end
81
81
  assert_equal user_invitation_path, current_path
@@ -85,7 +85,7 @@ class InvitationTest < ActionDispatch::IntegrationTest
85
85
 
86
86
  test 'not authenticated user with valid data should be able to change his password' do
87
87
  user = User.invite!(:email => "valid@email.com")
88
- set_password :invitation_token => user.invitation_token
88
+ set_password :invitation_token => Thread.current[:token]
89
89
 
90
90
  assert_equal root_path, current_path
91
91
  assert page.has_css?('p#notice', :text => 'Your password was set successfully. You are now signed in.')
@@ -94,7 +94,7 @@ class InvitationTest < ActionDispatch::IntegrationTest
94
94
 
95
95
  test 'after entering invalid data user should still be able to set his password' do
96
96
  user = User.invite!(:email => "valid@email.com")
97
- set_password :invitation_token => user.invitation_token do
97
+ set_password :invitation_token => Thread.current[:token] do
98
98
  fill_in 'Password confirmation', :with => 'other_password'
99
99
  end
100
100
  assert_equal user_invitation_path, current_path
@@ -108,7 +108,7 @@ class InvitationTest < ActionDispatch::IntegrationTest
108
108
 
109
109
  test 'sign in user automatically after setting it\'s password' do
110
110
  user = User.invite!(:email => "valid@email.com")
111
- set_password :invitation_token => user.invitation_token
111
+ set_password :invitation_token => Thread.current[:token]
112
112
  assert_equal root_path, current_path
113
113
  end
114
114
 
@@ -119,8 +119,7 @@ class InvitationTest < ActionDispatch::IntegrationTest
119
119
  fill_in 'user_email', :with => 'valid@email.com'
120
120
  click_button 'Send me reset password instructions'
121
121
 
122
- user.reload
123
- visit edit_user_password_path(:reset_password_token => user.reset_password_token)
122
+ visit edit_user_password_path(:reset_password_token => Thread.current[:token])
124
123
  set_password :visit => false, :button => 'Change my password'
125
124
 
126
125
  user.reload
@@ -53,7 +53,17 @@ class InvitationMailTest < ActionMailer::TestCase
53
53
 
54
54
  test 'body should have link to confirm the account' do
55
55
  host = ActionMailer::Base.default_url_options[:host]
56
- invitation_url_regexp = %r{<a href=\"http://#{host}/users/invitation/accept\?invitation_token=#{user.invitation_token}">}
57
- assert_match invitation_url_regexp, mail.body.decoded
56
+ body = mail.body.decoded
57
+ invitation_url_regexp = %r{<a href=\"http://#{host}/users/invitation/accept\?invitation_token=#{Thread.current[:token]}">}
58
+ assert_match invitation_url_regexp, body
59
+ end
60
+
61
+ test 'body should have link to confirm the account on resend' do
62
+ host = ActionMailer::Base.default_url_options[:host]
63
+ user
64
+ @user = User.find(user.id).invite!
65
+ body = mail.body.decoded
66
+ invitation_url_regexp = %r{<a href=\"http://#{host}/users/invitation/accept\?invitation_token=#{Thread.current[:token]}">}
67
+ assert_match invitation_url_regexp, body
58
68
  end
59
69
  end
@@ -11,18 +11,39 @@ class InvitableTest < ActiveSupport::TestCase
11
11
  assert_nil new_user.invitation_token
12
12
  end
13
13
 
14
- test 'should not regenerate invitation token each time' do
14
+ test 'should not generate the raw invitation token after creating a record' do
15
+ assert_nil new_user.raw_invitation_token
16
+ end
17
+
18
+ test 'should regenerate invitation token each time' do
15
19
  user = new_user
16
20
  user.invite!
17
21
  token = user.invitation_token
18
22
  assert_not_nil user.invitation_token
19
23
  assert_not_nil user.invitation_created_at
20
24
  3.times do
25
+ user = User.find(user.id)
21
26
  user.invite!
22
- assert_equal token, user.invitation_token
27
+ assert_not_equal token, user.invitation_token
28
+ token = user.invitation_token
23
29
  end
24
30
  end
25
31
 
32
+ test 'should alias the invitation_token method with encrypted_invitation_token' do
33
+ user = new_user
34
+ user.invite!
35
+ assert_equal user.invitation_token, user.encrypted_invitation_token
36
+ end
37
+
38
+ test 'should return the correct raw_invitation_token ' do
39
+ user = new_user
40
+ raw, enc = Devise.token_generator.generate(user.class, :invitation_token)
41
+ #stub the generator so the tokens are the same
42
+ Devise.token_generator.stubs(:generate).returns([raw, enc])
43
+ user.invite!
44
+ assert_equal user.raw_invitation_token, raw
45
+ end
46
+
26
47
  test 'should set invitation created and sent at each time' do
27
48
  user = new_user
28
49
  user.invite!
@@ -32,22 +53,11 @@ class InvitableTest < ActiveSupport::TestCase
32
53
  3.times do
33
54
  user.invite!
34
55
  assert_not_equal old_invitation_sent_at, user.invitation_sent_at
35
- assert_not_equal old_invitation_created_at, user.invitation_sent_at
56
+ assert_not_equal old_invitation_created_at, user.invitation_created_at
36
57
  user.update_attributes(:invitation_sent_at => old_invitation_sent_at, :invitation_created_at => old_invitation_created_at)
37
58
  end
38
59
  end
39
60
 
40
- test 'should not regenerate invitation token even after the invitation token is not valid' do
41
- User.stubs(:invite_for).returns(1.day)
42
- user = new_user
43
- user.invite!
44
- token = user.invitation_token
45
- user.invitation_created_at = 3.days.ago
46
- user.save
47
- user.invite!
48
- assert_equal token, user.invitation_token
49
- end
50
-
51
61
  test 'should test invitation sent at with invite_for configuration value' do
52
62
  user = User.invite!(:email => "valid@email.com")
53
63
 
@@ -87,18 +97,29 @@ class InvitableTest < ActiveSupport::TestCase
87
97
  end
88
98
  end
89
99
 
90
- test 'should invite with mutiple columns for invite key' do
100
+ test 'should invite with multiple columns for invite key' do
91
101
  User.stubs(:invite_key).returns(:email => Devise.email_regexp, :username => /\A.+\z/)
92
102
  user = User.invite!(:email => "valid@email.com", :username => "name")
93
103
  assert user.persisted?
94
104
  assert user.errors.empty?
95
105
  end
96
106
 
107
+ test 'should allow non-string columns for invite key' do
108
+ User.stubs(:invite_key).returns(:email => Devise.email_regexp, :profile_id => :present?.to_proc, :active => true)
109
+ user = User.invite!(:email => "valid@email.com", :profile_id => 1, :active => true)
110
+ assert user.persisted?
111
+ assert user.errors.empty?
112
+ end
113
+
97
114
  test 'should not invite with some missing columns when invite key is an array' do
98
- User.stubs(:invite_key).returns(:email => Devise.email_regexp, :username => /\A.+\z/)
115
+ User.stubs(:invite_key).returns(:email => Devise.email_regexp, :username => /\A.+\z/, :profile_id => :present?.to_proc, :active => true)
99
116
  user = User.invite!(:email => "valid@email.com")
100
117
  assert user.new_record?
101
118
  assert user.errors.present?
119
+ assert user.errors[:username]
120
+ assert user.errors[:profile_id]
121
+ assert user.errors[:active]
122
+ assert user.errors[:email].empty?
102
123
  end
103
124
 
104
125
  test 'should return mail object' do
@@ -113,14 +134,14 @@ class InvitableTest < ActiveSupport::TestCase
113
134
 
114
135
  test 'should set password and password confirmation from params' do
115
136
  invited_user = User.invite!(:email => "valid@email.com")
116
- user = User.accept_invitation!(:invitation_token => invited_user.invitation_token, :password => '123456789', :password_confirmation => '123456789')
137
+ user = User.accept_invitation!(:invitation_token => Thread.current[:token], :password => '123456789', :password_confirmation => '123456789')
117
138
  assert user.valid_password?('123456789')
118
139
  end
119
140
 
120
141
  test 'should set password and save the record' do
121
142
  user = User.invite!(:email => "valid@email.com")
122
143
  old_encrypted_password = user.encrypted_password
123
- user = User.accept_invitation!(:invitation_token => user.invitation_token, :password => '123456789', :password_confirmation => '123456789')
144
+ user = User.accept_invitation!(:invitation_token => Thread.current[:token], :password => '123456789', :password_confirmation => '123456789')
124
145
  assert_not_equal old_encrypted_password, user.encrypted_password
125
146
  end
126
147
 
@@ -147,10 +168,14 @@ class InvitableTest < ActiveSupport::TestCase
147
168
  test 'should clear invitation token while resetting the password' do
148
169
  user = User.invite!(:email => "valid@email.com")
149
170
  assert user.invited_to_sign_up?
150
- user.send(:generate_reset_password_token!)
171
+ token, user.reset_password_token = Devise.token_generator.generate(User, :reset_password_token)
172
+ user.reset_password_sent_at = Time.now.utc
173
+ user.save
174
+
151
175
  assert_present user.reset_password_token
152
176
  assert_present user.invitation_token
153
- User.reset_password_by_token(:reset_password_token => user.reset_password_token, :password => '123456789', :password_confirmation => '123456789')
177
+ User.reset_password_by_token(:reset_password_token => token, :password => '123456789', :password_confirmation => '123456789')
178
+ assert_nil user.reload.reset_password_token
154
179
  assert_nil user.reload.invitation_token
155
180
  assert !user.invited_to_sign_up?
156
181
  end
@@ -158,10 +183,14 @@ class InvitableTest < ActiveSupport::TestCase
158
183
  test 'should not accept invitation on failing to reset the password' do
159
184
  user = User.invite!(:email => "valid@email.com")
160
185
  assert user.invited_to_sign_up?
161
- user.send(:generate_reset_password_token!)
186
+ token, user.reset_password_token = Devise.token_generator.generate(User, :reset_password_token)
187
+ user.reset_password_sent_at = Time.now.utc
188
+ user.save
189
+
162
190
  assert_present user.reset_password_token
163
191
  assert_present user.invitation_token
164
- User.reset_password_by_token(:reset_password_token => user.reset_password_token, :password => '123456789', :password_confirmation => '12345678')
192
+ User.reset_password_by_token(:reset_password_token => token, :password => '123456789', :password_confirmation => '12345678')
193
+ assert_present user.reload.reset_password_token
165
194
  assert_present user.reload.invitation_token
166
195
  assert user.invited_to_sign_up?
167
196
  end
@@ -169,10 +198,13 @@ class InvitableTest < ActiveSupport::TestCase
169
198
  test 'should not set invitation_accepted_at if just resetting password' do
170
199
  user = User.create!(:email => "valid@email.com", :password => "123456780")
171
200
  assert !user.invited_to_sign_up?
172
- user.send(:generate_reset_password_token!)
201
+ token, user.reset_password_token = Devise.token_generator.generate(User, :reset_password_token)
202
+ user.reset_password_sent_at = Time.now.utc
203
+ user.save
204
+
173
205
  assert_present user.reset_password_token
174
206
  assert_nil user.invitation_token
175
- User.reset_password_by_token(:reset_password_token => user.reset_password_token, :password => '123456789', :password_confirmation => '123456789')
207
+ User.reset_password_by_token(:reset_password_token => token, :password => '123456789', :password_confirmation => '123456789')
176
208
  assert_nil user.reload.invitation_token
177
209
  assert_nil user.reload.invitation_accepted_at
178
210
  end
@@ -241,6 +273,9 @@ class InvitableTest < ActiveSupport::TestCase
241
273
  user = User.invite!(:email => "valid@email.com")
242
274
  assert_equal user, existing_user
243
275
  assert_equal ['has already been taken'], user.errors[:email]
276
+ same_user = User.invite!("email" => "valid@email.com")
277
+ assert_equal same_user, existing_user
278
+ assert_equal ['has already been taken'], same_user.errors[:email]
244
279
  end
245
280
 
246
281
  test 'should return a record with errors if user with pending invitation was found by e-mail' do
@@ -297,7 +332,7 @@ class InvitableTest < ActiveSupport::TestCase
297
332
  test 'should find a user to set his password based on invitation_token' do
298
333
  user = new_user
299
334
  user.invite!
300
- invited_user = User.accept_invitation!(:invitation_token => user.invitation_token)
335
+ invited_user = User.accept_invitation!(:invitation_token => Thread.current[:token])
301
336
  assert_equal invited_user, user
302
337
  end
303
338
 
@@ -318,7 +353,7 @@ class InvitableTest < ActiveSupport::TestCase
318
353
  invited_user = User.invite!(:email => "valid@email.com")
319
354
  invited_user.invitation_created_at = 2.days.ago
320
355
  invited_user.save(:validate => false)
321
- user = User.accept_invitation!(:invitation_token => invited_user.invitation_token)
356
+ user = User.accept_invitation!(:invitation_token => Thread.current[:token])
322
357
  assert_equal user, invited_user
323
358
  assert_equal ["is invalid"], user.errors[:invitation_token]
324
359
  end
@@ -336,7 +371,7 @@ class InvitableTest < ActiveSupport::TestCase
336
371
  user.invite!
337
372
 
338
373
  invited_user = User.accept_invitation!(
339
- :invitation_token => user.invitation_token,
374
+ :invitation_token => Thread.current[:token],
340
375
  :password => 'new_password',
341
376
  :password_confirmation => 'new_password'
342
377
  )
@@ -350,16 +385,34 @@ class InvitableTest < ActiveSupport::TestCase
350
385
  user.invite!
351
386
 
352
387
  invited_user = User.accept_invitation!(
353
- :invitation_token => user.invitation_token,
388
+ :invitation_token => Thread.current[:token],
354
389
  :password => 'new_password',
355
390
  :password_confirmation => 'new_password',
356
391
  :username => 'a'*50
357
392
  )
358
393
  assert invited_user.errors[:username].present?
359
394
 
395
+ user.reload
360
396
  assert !user.valid_password?('new_password')
361
397
  end
362
398
 
399
+ test 'should set other attributes on accepting invitation' do
400
+ user = new_user(:password => nil, :password_confirmation => nil)
401
+ user.invite!
402
+
403
+ invited_user = User.accept_invitation!(
404
+ :invitation_token => Thread.current[:token],
405
+ :password => 'new_password',
406
+ :password_confirmation => 'new_password',
407
+ :username => 'a'
408
+ )
409
+ assert invited_user.errors[:username].blank?
410
+
411
+ user.reload
412
+ assert_equal 'a', user.username
413
+ assert user.valid_password?('new_password')
414
+ end
415
+
363
416
  test 'should not confirm user on invite' do
364
417
  user = new_user
365
418
 
@@ -478,8 +531,9 @@ class InvitableTest < ActiveSupport::TestCase
478
531
 
479
532
  test "user.invite! should strip whitespace from the class's strip_whitespace_keys" do
480
533
  # Devise default is email
481
- user = User.invite!(:email => " valid@email.com ")
534
+ user = User.invite!(:email => " valid@email.com ", :active => true)
482
535
  assert user.email == "valid@email.com"
536
+ assert user.active == true
483
537
  end
484
538
 
485
539
  test 'should pass validation before accept if field is required in post-invited instance' do
data/test/models_test.rb CHANGED
@@ -70,5 +70,9 @@ class ModelsTest < ActiveSupport::TestCase
70
70
  test 'invitable attributes' do
71
71
  assert_nil User.new.invitation_token
72
72
  assert_nil User.new.invitation_sent_at
73
+ #raw token
74
+ assert_nil User.new.raw_invitation_token
75
+ #encrypted token - alias to invitation token
76
+ assert_nil User.new.encrypted_invitation_token
73
77
  end
74
78
  end
@@ -26,15 +26,17 @@ class User < PARENT_MODEL_CLASS
26
26
  field :invited_by_id, :type => Integer
27
27
  field :invited_by_type, :type => String
28
28
 
29
-
30
29
  field :username
30
+ field :profile_id
31
+ field :active
32
+
31
33
  validates_presence_of :email
32
34
  validates_presence_of :encrypted_password, :if => :password_required?
33
35
  end
34
36
 
35
37
  devise :database_authenticatable, :registerable, :validatable, :confirmable, :invitable, :recoverable
36
38
 
37
- attr_accessor :callback_works, :bio
39
+ attr_accessor :callback_works, :bio, :token
38
40
  validates :username, :length => { :maximum => 20 }
39
41
 
40
42
  attr_accessor :testing_accepting_or_not_invited
@@ -48,4 +50,9 @@ class User < PARENT_MODEL_CLASS
48
50
  after_invitation_accepted do |object|
49
51
  object.callback_works = true
50
52
  end
53
+
54
+ def send_devise_notification(method, raw, *args)
55
+ Thread.current[:token] = raw
56
+ super
57
+ end
51
58
  end
@@ -1,9 +1,15 @@
1
1
  # Use this hook to configure devise mailer, warden hooks and so forth.
2
2
  # Many of these configuration options can be set straight in your model.
3
3
  Devise.setup do |config|
4
+ # The secret key used by Devise. Devise uses this key to generate
5
+ # random tokens. Changing this key will render invalid all existing
6
+ # confirmation, reset password and unlock tokens in the database.
7
+ config.secret_key = 'e5151770530bba4c845256482be72bf598f723bebc057f4b8c15a7e63d7ffb61e7b8a0615e92a78127498ac509c43589b39bd13df397a4a2a7b3796c836e6ef8'
8
+
4
9
  # ==> Mailer Configuration
5
10
  # Configure the e-mail address which will be shown in Devise::Mailer,
6
- # note that it will be overwritten if you use your own mailer class with default "from" parameter.
11
+ # note that it will be overwritten if you use your own mailer class
12
+ # with default "from" parameter.
7
13
  config.mailer_sender = "please-change-me-at-config-initializers-devise@example.com"
8
14
 
9
15
  # Configure the class responsible to send e-mails.
@@ -16,6 +16,8 @@ class CreateTables < ActiveRecord::Migration
16
16
  t.string :unconfirmed_email # Only if using reconfirmable
17
17
 
18
18
  t.string :username
19
+ t.integer :profile_id
20
+ t.boolean :active
19
21
 
20
22
  ## Invitable
21
23
  t.string :invitation_token, :limit => 60
metadata CHANGED
@@ -1,13 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: devise_invitable
3
3
  version: !ruby/object:Gem::Version
4
- hash: 29
5
- prerelease:
6
- segments:
7
- - 1
8
- - 2
9
- - 1
10
- version: 1.2.1
4
+ version: 1.3.1
11
5
  platform: ruby
12
6
  authors:
13
7
  - Sergio Cambra
@@ -15,21 +9,15 @@ autorequire:
15
9
  bindir: bin
16
10
  cert_chain: []
17
11
 
18
- date: 2013-08-22 00:00:00 Z
12
+ date: 2013-11-05 00:00:00 Z
19
13
  dependencies:
20
14
  - !ruby/object:Gem::Dependency
21
15
  name: bundler
22
16
  prerelease: false
23
17
  requirement: &id001 !ruby/object:Gem::Requirement
24
- none: false
25
18
  requirements:
26
19
  - - ">="
27
20
  - !ruby/object:Gem::Version
28
- hash: 19
29
- segments:
30
- - 1
31
- - 1
32
- - 0
33
21
  version: 1.1.0
34
22
  type: :development
35
23
  version_requirements: *id001
@@ -37,21 +25,12 @@ dependencies:
37
25
  name: actionmailer
38
26
  prerelease: false
39
27
  requirement: &id002 !ruby/object:Gem::Requirement
40
- none: false
41
28
  requirements:
42
29
  - - ">="
43
30
  - !ruby/object:Gem::Version
44
- hash: 3
45
- segments:
46
- - 3
47
- - 2
48
- - 6
49
31
  version: 3.2.6
50
32
  - - <
51
33
  - !ruby/object:Gem::Version
52
- hash: 9
53
- segments:
54
- - 5
55
34
  version: "5"
56
35
  type: :runtime
57
36
  version_requirements: *id002
@@ -59,16 +38,10 @@ dependencies:
59
38
  name: devise
60
39
  prerelease: false
61
40
  requirement: &id003 !ruby/object:Gem::Requirement
62
- none: false
63
41
  requirements:
64
- - - ~>
42
+ - - ">="
65
43
  - !ruby/object:Gem::Version
66
- hash: 7
67
- segments:
68
- - 3
69
- - 0
70
- - 0
71
- version: 3.0.0
44
+ version: 3.1.0
72
45
  type: :runtime
73
46
  version_requirements: *id003
74
47
  description: It adds support for send invitations by email (it requires to be authenticated) and accept the invitation by setting a password.
@@ -108,6 +81,7 @@ files:
108
81
  - lib/generators/mongoid/devise_invitable_generator.rb
109
82
  - LICENSE
110
83
  - README.rdoc
84
+ - CHANGELOG
111
85
  - test/functional/controller_helpers_test.rb
112
86
  - test/functional/registrations_controller_test.rb
113
87
  - test/generators/views_generator_test.rb
@@ -160,7 +134,9 @@ files:
160
134
  - test/routes_test.rb
161
135
  - test/test_helper.rb
162
136
  homepage: https://github.com/scambra/devise_invitable
163
- licenses: []
137
+ licenses:
138
+ - MIT
139
+ metadata: {}
164
140
 
165
141
  post_install_message:
166
142
  rdoc_options:
@@ -170,33 +146,21 @@ rdoc_options:
170
146
  require_paths:
171
147
  - lib
172
148
  required_ruby_version: !ruby/object:Gem::Requirement
173
- none: false
174
149
  requirements:
175
150
  - - ">="
176
151
  - !ruby/object:Gem::Version
177
- hash: 59
178
- segments:
179
- - 1
180
- - 8
181
- - 6
182
152
  version: 1.8.6
183
153
  required_rubygems_version: !ruby/object:Gem::Requirement
184
- none: false
185
154
  requirements:
186
155
  - - ">="
187
156
  - !ruby/object:Gem::Version
188
- hash: 23
189
- segments:
190
- - 1
191
- - 3
192
- - 6
193
157
  version: 1.3.6
194
158
  requirements: []
195
159
 
196
160
  rubyforge_project:
197
- rubygems_version: 1.8.23
161
+ rubygems_version: 2.0.7
198
162
  signing_key:
199
- specification_version: 3
163
+ specification_version: 4
200
164
  summary: An invitation strategy for Devise
201
165
  test_files:
202
166
  - test/functional/controller_helpers_test.rb