devise_invitable 1.3.4 → 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 +4 -4
- data/CHANGELOG +6 -0
- data/README.rdoc +30 -5
- data/app/controllers/devise/invitations_controller.rb +31 -20
- data/app/views/devise/mailer/invitation_instructions.html.erb +4 -5
- data/config/locales/en.yml +12 -5
- data/lib/devise_invitable/model.rb +44 -20
- data/lib/devise_invitable/parameter_sanitizer.rb +1 -1
- data/lib/devise_invitable/rails.rb +6 -4
- data/lib/devise_invitable/version.rb +1 -1
- data/lib/devise_invitable.rb +5 -0
- data/lib/generators/active_record/devise_invitable_generator.rb +1 -1
- data/lib/generators/active_record/templates/migration.rb +7 -3
- data/lib/generators/devise_invitable/install_generator.rb +5 -0
- data/lib/generators/devise_invitable/templates/simple_form_for/invitations/edit.html.erb +2 -2
- data/lib/generators/devise_invitable/templates/simple_form_for/invitations/new.html.erb +2 -2
- data/test/functional/controller_helpers_test.rb +3 -3
- data/test/functional/registrations_controller_test.rb +6 -6
- data/test/generators_test.rb +6 -6
- data/test/integration/invitation_remove_test.rb +1 -1
- data/test/integration/invitation_test.rb +51 -6
- data/test/integration_tests_helper.rb +3 -3
- data/test/models/invitable_test.rb +81 -25
- data/test/rails_app/app/controllers/application_controller.rb +7 -0
- data/test/rails_app/app/controllers/free_invitations_controller.rb +6 -0
- data/test/rails_app/app/views/devise/sessions/new.html.erb +17 -0
- data/test/rails_app/config/initializers/devise.rb +5 -0
- metadata +8 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA1:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9ebdddd2e1426fdde9ad700f7f60e4cedf3e48ab
|
|
4
|
+
data.tar.gz: 3a783c0d76c73a4a6af2c07cda15b80904fecdcf
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b85b1eabee1011a5175f98ca92f05f39a1f49e3da2666f17bafa8a87d22115700d6f7beee89261a5096d70dcfeec473834dc43bfcaa5a967b63fc78b7789bb7f
|
|
7
|
+
data.tar.gz: 483b574690f8a3064b7d4f941a7fad4e321fe89d677192b82e68d0d0f3feba49d92125054f17e89521c2810e9feb80de12ee135895728d7d1f633bdd845845ea
|
data/CHANGELOG
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
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
|
+
|
|
3
|
+
= 1.4.0
|
|
4
|
+
Override active_for_authentication? and inactive_message instead of valid_password?
|
|
5
|
+
To use counter_cache, invited_by_counter_cache must be set, no more checking of invitations_count to enable counter cache
|
|
6
|
+
|
|
1
7
|
= 1.3.0
|
|
2
8
|
Now devise 3.1 compatible, @token must be used instead of @resource.invitation_token in mail views
|
|
3
9
|
|
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
|
|
|
@@ -16,7 +16,7 @@ Install DeviseInvitable gem, it will also install dependencies (such as devise a
|
|
|
16
16
|
Add DeviseInvitable to your Gemfile (and Devise if you weren't using them):
|
|
17
17
|
|
|
18
18
|
gem 'devise', '>= 2.0.0'
|
|
19
|
-
gem 'devise_invitable', '~> 1.
|
|
19
|
+
gem 'devise_invitable', '~> 1.3.4'
|
|
20
20
|
|
|
21
21
|
=== Automatic installation
|
|
22
22
|
|
|
@@ -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
|
|
@@ -233,11 +235,16 @@ To send an invitation to a user, use the <tt>invite!</tt> class method. <tt>:ema
|
|
|
233
235
|
|
|
234
236
|
If you want to create the invitation but not send it, you can set <tt>skip_invitation</tt> to true.
|
|
235
237
|
|
|
236
|
-
User.invite!(:email => "new_user@example.com", :name => "John Doe") do |u|
|
|
238
|
+
user = User.invite!(:email => "new_user@example.com", :name => "John Doe") do |u|
|
|
237
239
|
u.skip_invitation = true
|
|
238
240
|
end
|
|
239
241
|
# => the record will be created, but the invitation email will not be sent
|
|
240
242
|
|
|
243
|
+
When generating the <tt>accept_user_invitation_url</tt> yourself, you must use the <tt>raw_invitation_token</tt>
|
|
244
|
+
the value is temporarily available when you invite a user and will be decrypted when recieved.
|
|
245
|
+
|
|
246
|
+
accept_user_invitation_url(:invitation_token => user.raw_invitation_token)
|
|
247
|
+
|
|
241
248
|
When skip_invitation is used, you must also then set the invitation_sent_at field when the user is sent their
|
|
242
249
|
token. Failure to do so will yield "Invalid invitation token" errors when the user attempts to accept the invite.
|
|
243
250
|
You can set it like so:
|
|
@@ -249,9 +256,16 @@ You can add :skip_invitation to attributes hash if skip_invitation is added to a
|
|
|
249
256
|
User.invite!(:email => "new_user@example.com", :name => "John Doe", :skip_invitation => true)
|
|
250
257
|
# => the record will be created, but the invitation email will not be sent
|
|
251
258
|
|
|
252
|
-
Skip_invitation skips sending the email, but sets invitation_token, so invited_to_sign_up
|
|
259
|
+
Skip_invitation skips sending the email, but sets invitation_token, so <tt>invited_to_sign_up?</tt> on the
|
|
253
260
|
resulting user returns true.
|
|
254
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
|
+
|
|
264
|
+
**Warning**
|
|
265
|
+
|
|
266
|
+
When using skip_invitation you must send the email with the user object instance that generated the tokens, as
|
|
267
|
+
user.raw_invitation_token is available only to the instance and is not persisted in the database.
|
|
268
|
+
|
|
255
269
|
You can send an invitation to an existing user if your workflow creates them separately:
|
|
256
270
|
|
|
257
271
|
user = User.find(42)
|
|
@@ -261,6 +275,8 @@ You can also set <tt>invited_by</tt> when using the <tt>invite!</tt> class metho
|
|
|
261
275
|
|
|
262
276
|
User.invite!({:email => "new_user@example.com"}, current_user) # current_user will be set as invited_by
|
|
263
277
|
|
|
278
|
+
|
|
279
|
+
|
|
264
280
|
=== Accept an invitation
|
|
265
281
|
|
|
266
282
|
To accept an invitation with a token use the <tt>accept_invitation!</tt> class method. <tt>:invitation_token</tt> must be present in the parameters hash. You can also include other attributes in the hash.
|
|
@@ -283,8 +299,9 @@ The callbacks support all options and arguments available to the standard callba
|
|
|
283
299
|
|
|
284
300
|
A pair of scopes to find those users that have accepted, and those that have not accepted, invitations are defined:
|
|
285
301
|
|
|
286
|
-
User.invitation_accepted
|
|
302
|
+
User.invitation_accepted # => returns all Users for whom the invitation_accepted_at attribute is not nil
|
|
287
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
|
|
288
305
|
|
|
289
306
|
== Integration in a Rails application
|
|
290
307
|
|
|
@@ -343,6 +360,7 @@ DeviseInvitable uses flash messages with I18n with the flash keys <tt>:send_inst
|
|
|
343
360
|
send_instructions: 'An invitation email has been sent to %{email}.'
|
|
344
361
|
invitation_token_invalid: 'The invitation token provided is not valid!'
|
|
345
362
|
updated: 'Your password was set successfully. You are now signed in.'
|
|
363
|
+
updated_not_active: 'Your password was set successfully.'
|
|
346
364
|
|
|
347
365
|
You can also create distinct messages based on the resource you've configured using the singular name given in routes:
|
|
348
366
|
|
|
@@ -353,6 +371,7 @@ You can also create distinct messages based on the resource you've configured us
|
|
|
353
371
|
send_instructions: 'A new user invitation has been sent to %{email}.'
|
|
354
372
|
invitation_token_invalid: 'Your invitation token is not valid!'
|
|
355
373
|
updated: 'Welcome on board! You are now signed in.'
|
|
374
|
+
updated_not_active: 'Welcome on board! Sign in to continue.'
|
|
356
375
|
|
|
357
376
|
The DeviseInvitable mailer uses the same pattern as Devise to create mail subject messages:
|
|
358
377
|
|
|
@@ -369,6 +388,12 @@ Take a look at the generated locale file (in <tt>config/locales/devise_invitable
|
|
|
369
388
|
|
|
370
389
|
DeviseInvitable supports ActiveRecord and Mongoid, like Devise.
|
|
371
390
|
|
|
391
|
+
== Wiki
|
|
392
|
+
|
|
393
|
+
It's possible to find additional information about DeviseInvitable on the Wiki:
|
|
394
|
+
|
|
395
|
+
https://github.com/scambra/devise_invitable/wiki
|
|
396
|
+
|
|
372
397
|
== Testing
|
|
373
398
|
|
|
374
399
|
To test DeviseInvitable for the ActiveRecord ORM with RVM, Ruby 1.9.2, and Rubygems 1.8.17:
|
|
@@ -15,11 +15,15 @@ class Devise::InvitationsController < DeviseController
|
|
|
15
15
|
# POST /resource/invitation
|
|
16
16
|
def create
|
|
17
17
|
self.resource = invite_resource
|
|
18
|
+
resource_invited = resource.errors.empty?
|
|
18
19
|
|
|
19
|
-
if
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
yield resource if block_given?
|
|
21
|
+
|
|
22
|
+
if resource_invited
|
|
23
|
+
if is_flashing_format? && self.resource.invitation_sent_at
|
|
24
|
+
set_flash_message :notice, :send_instructions, :email => self.resource.email
|
|
25
|
+
end
|
|
26
|
+
respond_with resource, :location => after_invite_path_for(current_inviter)
|
|
23
27
|
else
|
|
24
28
|
respond_with_navigational(resource) { render :new }
|
|
25
29
|
end
|
|
@@ -34,50 +38,57 @@ class Devise::InvitationsController < DeviseController
|
|
|
34
38
|
# PUT /resource/invitation
|
|
35
39
|
def update
|
|
36
40
|
self.resource = accept_resource
|
|
41
|
+
invitation_accepted = resource.errors.empty?
|
|
42
|
+
|
|
43
|
+
yield resource if block_given?
|
|
37
44
|
|
|
38
|
-
if
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
45
|
+
if invitation_accepted
|
|
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
|
|
44
55
|
else
|
|
45
56
|
respond_with_navigational(resource){ render :edit }
|
|
46
57
|
end
|
|
47
58
|
end
|
|
48
|
-
|
|
59
|
+
|
|
49
60
|
# GET /resource/invitation/remove?invitation_token=abcdef
|
|
50
61
|
def destroy
|
|
51
62
|
resource.destroy
|
|
52
|
-
set_flash_message :notice, :invitation_removed
|
|
63
|
+
set_flash_message :notice, :invitation_removed if is_flashing_format?
|
|
53
64
|
redirect_to after_sign_out_path_for(resource_name)
|
|
54
65
|
end
|
|
55
66
|
|
|
56
67
|
protected
|
|
57
68
|
|
|
58
|
-
def invite_resource
|
|
59
|
-
resource_class.invite!(invite_params, current_inviter)
|
|
69
|
+
def invite_resource(&block)
|
|
70
|
+
resource_class.invite!(invite_params, current_inviter, &block)
|
|
60
71
|
end
|
|
61
|
-
|
|
72
|
+
|
|
62
73
|
def accept_resource
|
|
63
74
|
resource_class.accept_invitation!(update_resource_params)
|
|
64
75
|
end
|
|
65
76
|
|
|
66
77
|
def current_inviter
|
|
67
|
-
|
|
78
|
+
authenticate_inviter!
|
|
68
79
|
end
|
|
69
80
|
|
|
70
81
|
def has_invitations_left?
|
|
71
82
|
unless current_inviter.nil? || current_inviter.has_invitations_left?
|
|
72
83
|
self.resource = resource_class.new
|
|
73
|
-
set_flash_message :alert, :no_invitations_remaining
|
|
84
|
+
set_flash_message :alert, :no_invitations_remaining if is_flashing_format?
|
|
74
85
|
respond_with_navigational(resource) { render :new }
|
|
75
86
|
end
|
|
76
87
|
end
|
|
77
|
-
|
|
88
|
+
|
|
78
89
|
def resource_from_invitation_token
|
|
79
90
|
unless params[:invitation_token] && self.resource = resource_class.find_by_invitation_token(params[:invitation_token], true)
|
|
80
|
-
set_flash_message(:alert, :invitation_token_invalid)
|
|
91
|
+
set_flash_message(:alert, :invitation_token_invalid) if is_flashing_format?
|
|
81
92
|
redirect_to after_sign_out_path_for(resource_name)
|
|
82
93
|
end
|
|
83
94
|
end
|
|
@@ -89,6 +100,6 @@ class Devise::InvitationsController < DeviseController
|
|
|
89
100
|
def update_resource_params
|
|
90
101
|
devise_parameter_sanitizer.sanitize(:accept_invitation)
|
|
91
102
|
end
|
|
92
|
-
|
|
103
|
+
|
|
93
104
|
end
|
|
94
105
|
|
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
<p
|
|
1
|
+
<p><%= t("devise.mailer.invitation_instructions.hello", email: @resource.email) %></p>
|
|
2
2
|
|
|
3
|
-
<p
|
|
3
|
+
<p><%= t("devise.mailer.invitation_instructions.someone_invited_you", url: root_url) %></p>
|
|
4
4
|
|
|
5
|
-
<p><%= link_to
|
|
5
|
+
<p><%= link_to t("devise.mailer.invitation_instructions.accept"), accept_invitation_url(@resource, :invitation_token => @token) %></p>
|
|
6
6
|
|
|
7
|
-
<p
|
|
8
|
-
Your account won't be created until you access the link above and set your password.</p>
|
|
7
|
+
<p><%= t("devise.mailer.invitation_instructions.ignore").html_safe %></p>
|
data/config/locales/en.yml
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
en:
|
|
2
2
|
devise:
|
|
3
|
+
failure:
|
|
4
|
+
invited: "You have a pending invitation, accept it to finish creating your account."
|
|
3
5
|
invitations:
|
|
4
|
-
send_instructions:
|
|
5
|
-
invitation_token_invalid:
|
|
6
|
-
updated:
|
|
6
|
+
send_instructions: "An invitation email has been sent to %{email}."
|
|
7
|
+
invitation_token_invalid: "The invitation token provided is not valid!"
|
|
8
|
+
updated: "Your password was set successfully. You are now signed in."
|
|
9
|
+
updated_not_active: "Your password was set successfully."
|
|
7
10
|
no_invitations_remaining: "No invitations remaining"
|
|
8
|
-
invitation_removed:
|
|
11
|
+
invitation_removed: "Your invitation was removed."
|
|
9
12
|
new:
|
|
10
13
|
header: "Send invitation"
|
|
11
14
|
submit_button: "Send an invitation"
|
|
@@ -14,4 +17,8 @@ en:
|
|
|
14
17
|
submit_button: "Set my password"
|
|
15
18
|
mailer:
|
|
16
19
|
invitation_instructions:
|
|
17
|
-
subject:
|
|
20
|
+
subject: "Invitation instructions"
|
|
21
|
+
hello: "Hello %{email}"
|
|
22
|
+
someone_invited_you: "Someone has invited you to %{url}, you can accept it through the link below."
|
|
23
|
+
accept: "Accept invitation"
|
|
24
|
+
ignore: "If you don't want to accept the invitation, please ignore this email.<br />Your account won't be created until you access the link above and set your password."
|
|
@@ -34,12 +34,8 @@ module Devise
|
|
|
34
34
|
else
|
|
35
35
|
{:polymorphic => true}
|
|
36
36
|
end
|
|
37
|
-
if defined?(ActiveRecord) && self < ActiveRecord::Base
|
|
37
|
+
if defined?(ActiveRecord) && defined?(ActiveRecord::Base) && self < ActiveRecord::Base
|
|
38
38
|
counter_cache = Devise.invited_by_counter_cache
|
|
39
|
-
if !counter_cache && Devise.invited_by_class_name
|
|
40
|
-
klass = Devise.invited_by_class_name.constantize
|
|
41
|
-
counter_cache = klass.table_exists? && klass.columns_hash['invitations_count'].try('name')
|
|
42
|
-
end
|
|
43
39
|
belongs_to_options.merge! :counter_cache => counter_cache if counter_cache
|
|
44
40
|
end
|
|
45
41
|
belongs_to :invited_by, belongs_to_options
|
|
@@ -49,11 +45,13 @@ module Devise
|
|
|
49
45
|
|
|
50
46
|
attr_writer :skip_password
|
|
51
47
|
|
|
52
|
-
scope :
|
|
48
|
+
scope :no_active_invitation, lambda { where(:invitation_token => nil) }
|
|
53
49
|
if defined?(Mongoid) && defined?(Mongoid::Document) && self < Mongoid::Document
|
|
50
|
+
scope :created_by_invite, lambda { where(:invitation_sent_at.ne => nil) }
|
|
54
51
|
scope :invitation_not_accepted, lambda { where(:invitation_accepted_at => nil, :invitation_token.ne => nil) }
|
|
55
52
|
scope :invitation_accepted, lambda { where(:invitation_accepted_at.ne => nil) }
|
|
56
53
|
else
|
|
54
|
+
scope :created_by_invite, lambda { where(arel_table[:invitation_sent_at].not_eq(nil)) }
|
|
57
55
|
scope :invitation_not_accepted, lambda { where(arel_table[:invitation_token].not_eq(nil)).where(:invitation_accepted_at => nil) }
|
|
58
56
|
scope :invitation_accepted, lambda { where(arel_table[:invitation_accepted_at].not_eq(nil)) }
|
|
59
57
|
|
|
@@ -89,6 +87,11 @@ module Devise
|
|
|
89
87
|
end
|
|
90
88
|
end
|
|
91
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
|
+
|
|
92
95
|
# Verifies whether a user has been invited or not
|
|
93
96
|
def invited_to_sign_up?
|
|
94
97
|
persisted? && invitation_token.present?
|
|
@@ -106,26 +109,28 @@ module Devise
|
|
|
106
109
|
|
|
107
110
|
# Reset invitation token and send invitation again
|
|
108
111
|
def invite!(invited_by = nil)
|
|
112
|
+
# This is an order-dependant assignment, this can't be moved
|
|
109
113
|
was_invited = invited_to_sign_up?
|
|
110
114
|
|
|
111
115
|
# Required to workaround confirmable model's confirmation_required? method
|
|
112
116
|
# being implemented to check for non-nil value of confirmed_at
|
|
113
|
-
if
|
|
117
|
+
if new_record_and_responds_to?(:confirmation_required?)
|
|
114
118
|
def self.confirmation_required?; false; end
|
|
115
119
|
end
|
|
116
120
|
|
|
117
|
-
|
|
121
|
+
yield self if block_given?
|
|
122
|
+
generate_invitation_token if no_token_present_or_skip_invitation?
|
|
118
123
|
self.invitation_created_at = Time.now.utc
|
|
119
|
-
self.invitation_sent_at = self.invitation_created_at unless
|
|
124
|
+
self.invitation_sent_at = self.invitation_created_at unless skip_invitation
|
|
120
125
|
self.invited_by = invited_by if invited_by
|
|
121
126
|
|
|
122
127
|
# Call these before_validate methods since we aren't validating on save
|
|
123
|
-
self.downcase_keys if
|
|
124
|
-
self.strip_whitespace if
|
|
128
|
+
self.downcase_keys if new_record_and_responds_to?(:downcase_keys)
|
|
129
|
+
self.strip_whitespace if new_record_and_responds_to?(:strip_whitespace)
|
|
125
130
|
|
|
126
131
|
if save(:validate => false)
|
|
127
132
|
self.invited_by.decrement_invitation_limit! if !was_invited and self.invited_by.present?
|
|
128
|
-
deliver_invitation unless
|
|
133
|
+
deliver_invitation unless skip_invitation
|
|
129
134
|
end
|
|
130
135
|
end
|
|
131
136
|
|
|
@@ -138,11 +143,15 @@ module Devise
|
|
|
138
143
|
|
|
139
144
|
# Only verify password when is not invited
|
|
140
145
|
def valid_password?(password)
|
|
141
|
-
super unless
|
|
146
|
+
super unless block_from_invitation?
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def unauthenticated_message
|
|
150
|
+
block_from_invitation? ? :invited : super
|
|
142
151
|
end
|
|
143
152
|
|
|
144
153
|
def after_password_reset
|
|
145
|
-
super
|
|
154
|
+
super if defined?(super)
|
|
146
155
|
accept_invitation! if invited_to_sign_up?
|
|
147
156
|
end
|
|
148
157
|
|
|
@@ -171,10 +180,17 @@ module Devise
|
|
|
171
180
|
protected
|
|
172
181
|
# Overriding the method in Devise's :validatable module so password is not required on inviting
|
|
173
182
|
def password_required?
|
|
174
|
-
|
|
183
|
+
!skip_password && super
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
def skip_password
|
|
187
|
+
@skip_password ||= false
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
def block_from_invitation?
|
|
191
|
+
invited_to_sign_up?
|
|
175
192
|
end
|
|
176
193
|
|
|
177
|
-
|
|
178
194
|
# Checks if the invitation for the user is within the limit time.
|
|
179
195
|
# We do this by calculating if the difference between today and the
|
|
180
196
|
# invitation sent date does not exceed the invite for time configured.
|
|
@@ -211,6 +227,14 @@ module Devise
|
|
|
211
227
|
generate_invitation_token && save(:validate => false)
|
|
212
228
|
end
|
|
213
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
|
+
|
|
214
238
|
module ClassMethods
|
|
215
239
|
# Return fields to invite
|
|
216
240
|
def invite_key_fields
|
|
@@ -253,13 +277,11 @@ module Devise
|
|
|
253
277
|
end
|
|
254
278
|
|
|
255
279
|
def invite!(attributes={}, invited_by=nil, &block)
|
|
256
|
-
|
|
257
|
-
invitable
|
|
280
|
+
_invite(attributes.with_indifferent_access, invited_by, &block).first
|
|
258
281
|
end
|
|
259
282
|
|
|
260
283
|
def invite_mail!(attributes={}, invited_by=nil, &block)
|
|
261
|
-
|
|
262
|
-
mail
|
|
284
|
+
_invite(attributes, invited_by, &block).last
|
|
263
285
|
end
|
|
264
286
|
|
|
265
287
|
# Attempt to find a user by it's invitation_token to set it's password.
|
|
@@ -301,6 +323,8 @@ module Devise
|
|
|
301
323
|
Devise::Models.config(self, :invitation_limit)
|
|
302
324
|
Devise::Models.config(self, :invite_key)
|
|
303
325
|
Devise::Models.config(self, :resend_invitation)
|
|
326
|
+
Devise::Models.config(self, :allow_insecure_sign_in_after_accept)
|
|
327
|
+
|
|
304
328
|
end
|
|
305
329
|
end
|
|
306
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)
|
|
@@ -7,12 +7,14 @@ module DeviseInvitable
|
|
|
7
7
|
|
|
8
8
|
# We use to_prepare instead of after_initialize here because Devise is a Rails engine; its
|
|
9
9
|
# mailer is reloaded like the rest of the user's app. Got to make sure that our mailer methods
|
|
10
|
-
# are included each time Devise
|
|
10
|
+
# are included each time Devise.mailer is (re)loaded.
|
|
11
11
|
config.to_prepare do
|
|
12
|
-
|
|
13
|
-
Devise
|
|
12
|
+
Devise.mailer.send :include, DeviseInvitable::Mailer
|
|
13
|
+
unless Devise.mailer.ancestors.include?(Devise::Mailers::Helpers)
|
|
14
|
+
Devise.mailer.send :include, Devise::Mailers::Helpers
|
|
15
|
+
end
|
|
14
16
|
end
|
|
15
|
-
# extend mapping with after_initialize
|
|
17
|
+
# extend mapping with after_initialize because it's not reloaded
|
|
16
18
|
config.after_initialize do
|
|
17
19
|
Devise::Mapping.send :include, DeviseInvitable::Mapping
|
|
18
20
|
Devise::ParameterSanitizer.send :include, DeviseInvitable::ParameterSanitizer
|
data/lib/devise_invitable.rb
CHANGED
|
@@ -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,7 +6,7 @@ module ActiveRecord
|
|
|
6
6
|
source_root File.expand_path("../templates", __FILE__)
|
|
7
7
|
|
|
8
8
|
def copy_devise_migration
|
|
9
|
-
migration_template "migration.rb", "db/migrate/devise_invitable_add_to_#{table_name}"
|
|
9
|
+
migration_template "migration.rb", "db/migrate/devise_invitable_add_to_#{table_name}.rb"
|
|
10
10
|
end
|
|
11
11
|
end
|
|
12
12
|
end
|
|
@@ -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, :
|
|
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, :
|
|
12
|
+
t.index :invitation_token, unique: true # for invitable
|
|
13
13
|
t.index :invited_by_id
|
|
14
14
|
end
|
|
15
15
|
|
|
@@ -22,8 +22,12 @@ 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, :
|
|
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
|
+
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 -%>
|
|
28
32
|
end
|
|
29
33
|
end
|
|
@@ -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,11 +1,11 @@
|
|
|
1
1
|
<h2><%= t 'devise.invitations.edit.header' %></h2>
|
|
2
2
|
|
|
3
|
-
<%= simple_form_for resource, :
|
|
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
|
|
|
7
7
|
<%= f.input :password %>
|
|
8
8
|
<%= f.input :password_confirmation %>
|
|
9
9
|
|
|
10
|
-
<%= f.submit t("devise.invitations.edit.submit_button") %>
|
|
10
|
+
<%= f.button :submit, t("devise.invitations.edit.submit_button") %>
|
|
11
11
|
<% end %>
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
<h2><%= t "devise.invitations.new.header" %></h2>
|
|
2
2
|
|
|
3
|
-
<%= simple_form_for resource, :
|
|
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| -%>
|
|
7
7
|
<%= f.input field %>
|
|
8
8
|
<% end -%>
|
|
9
9
|
|
|
10
|
-
<%= f.submit t("devise.invitations.new.submit_button") %>
|
|
10
|
+
<%= f.button :submit, t("devise.invitations.new.submit_button") %>
|
|
11
11
|
<% end %>
|
|
@@ -4,11 +4,11 @@ class ControllerHelpersTest < ActionController::TestCase
|
|
|
4
4
|
tests ApplicationController
|
|
5
5
|
|
|
6
6
|
test "after invite path defaults to after sign in path" do
|
|
7
|
-
assert_equal @controller.
|
|
7
|
+
assert_equal @controller.send(:after_sign_in_path_for, :user), @controller.after_invite_path_for(:user)
|
|
8
8
|
end
|
|
9
9
|
|
|
10
10
|
test "after accept path defaults to after sign in path" do
|
|
11
|
-
assert_equal @controller.
|
|
11
|
+
assert_equal @controller.send(:after_sign_in_path_for, :user), @controller.after_accept_path_for(:user)
|
|
12
12
|
end
|
|
13
13
|
|
|
14
14
|
test 'after invite path is customizable from application controller' do
|
|
@@ -36,4 +36,4 @@ class ControllerHelpersTest < ActionController::TestCase
|
|
|
36
36
|
assert Devise::InvitationsController.method_defined? :after_accept_path_for
|
|
37
37
|
assert !Devise::InvitationsController.instance_methods(false).include?(:after_accept_path_for)
|
|
38
38
|
end
|
|
39
|
-
end
|
|
39
|
+
end
|
|
@@ -25,7 +25,7 @@ class DeviseInvitable::RegistrationsControllerTest < ActionController::TestCase
|
|
|
25
25
|
sign_out @issuer
|
|
26
26
|
|
|
27
27
|
@invitee = User.where(:email => invitee_email).first
|
|
28
|
-
|
|
28
|
+
assert @invitee.encrypted_password.blank?, "the password should be unset"
|
|
29
29
|
|
|
30
30
|
# sign_up the invitee
|
|
31
31
|
assert_difference('ActionMailer::Base.deliveries.size') do
|
|
@@ -42,13 +42,13 @@ class DeviseInvitable::RegistrationsControllerTest < ActionController::TestCase
|
|
|
42
42
|
@invitee.save!
|
|
43
43
|
end
|
|
44
44
|
|
|
45
|
-
|
|
45
|
+
assert @invitee.encrypted_password.present?
|
|
46
46
|
assert_not_nil @invitee.invitation_accepted_at
|
|
47
47
|
assert_nil @invitee.invitation_token
|
|
48
|
-
|
|
49
|
-
|
|
48
|
+
assert @invitee.invited_by_id.present?
|
|
49
|
+
assert @invitee.invited_by_type.present?
|
|
50
50
|
assert !@invitee.confirmed?
|
|
51
|
-
|
|
51
|
+
assert @invitee.confirmation_token.present?
|
|
52
52
|
end
|
|
53
53
|
|
|
54
54
|
test "not invitable resources can register" do
|
|
@@ -58,7 +58,7 @@ class DeviseInvitable::RegistrationsControllerTest < ActionController::TestCase
|
|
|
58
58
|
post :create, :admin => {:email => invitee_email, :password => "1password"}
|
|
59
59
|
|
|
60
60
|
@invitee = Admin.where(:email => invitee_email).first
|
|
61
|
-
|
|
61
|
+
assert @invitee.encrypted_password.present?
|
|
62
62
|
end
|
|
63
63
|
|
|
64
64
|
test "missing params on a create should not cause an error" do
|
data/test/generators_test.rb
CHANGED
|
@@ -17,18 +17,18 @@ class GeneratorsTest < ActiveSupport::TestCase
|
|
|
17
17
|
|
|
18
18
|
test "rails g devise_invitable:install" do
|
|
19
19
|
@output = `cd #{RAILS_APP_PATH} && rails g devise_invitable:install -p`
|
|
20
|
-
assert @output.match(%r{(inject|insert)
|
|
21
|
-
assert @output.match(%r|create
|
|
20
|
+
assert @output.match(%r{(inject|insert).* config/initializers/devise\.rb\n})
|
|
21
|
+
assert @output.match(%r|create.* config/locales/devise_invitable\.en\.yml\n|)
|
|
22
22
|
end
|
|
23
23
|
|
|
24
24
|
test "rails g devise_invitable Octopussy" do
|
|
25
25
|
@output = `cd #{RAILS_APP_PATH} && rails g devise_invitable Octopussy -p`
|
|
26
|
-
assert @output.match(%r{(inject|insert)
|
|
27
|
-
assert @output.match(%r|invoke
|
|
26
|
+
assert @output.match(%r{(inject|insert).* app/models/octopussy\.rb\n})
|
|
27
|
+
assert @output.match(%r|invoke.* #{DEVISE_ORM}\n|)
|
|
28
28
|
if DEVISE_ORM == :active_record
|
|
29
|
-
assert @output.match(%r|create
|
|
29
|
+
assert @output.match(%r|create.* db/migrate/\d{14}_devise_invitable_add_to_octopussies\.rb\n|)
|
|
30
30
|
elsif DEVISE_ORM == :mongoid
|
|
31
|
-
assert !@output.match(%r|create
|
|
31
|
+
assert !@output.match(%r|create.* db/migrate/\d{14}_devise_invitable_add_to_octopussies\.rb\n|)
|
|
32
32
|
end
|
|
33
33
|
end
|
|
34
34
|
end
|
|
@@ -4,7 +4,7 @@ require 'integration_tests_helper'
|
|
|
4
4
|
class InvitationRemoveTest < ActionDispatch::IntegrationTest
|
|
5
5
|
|
|
6
6
|
test 'invited user can choose to remove his account/invite' do
|
|
7
|
-
|
|
7
|
+
User.invite!(:email => "valid@email.com")
|
|
8
8
|
|
|
9
9
|
# remove!
|
|
10
10
|
visit remove_user_invitation_path(:invitation_token => Thread.current[:token])
|
|
@@ -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
|
-
|
|
102
|
+
assert user.encrypted_password.blank?
|
|
84
103
|
end
|
|
85
104
|
|
|
86
105
|
test 'not authenticated user with valid data should be able to change his password' do
|
|
@@ -99,17 +118,33 @@ class InvitationTest < ActionDispatch::IntegrationTest
|
|
|
99
118
|
end
|
|
100
119
|
assert_equal user_invitation_path, current_path
|
|
101
120
|
assert page.has_css?('#error_explanation')
|
|
102
|
-
|
|
121
|
+
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
|
|
110
|
-
|
|
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
|
+
|
|
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
|
|
@@ -213,10 +248,20 @@ class InvitationTest < ActionDispatch::IntegrationTest
|
|
|
213
248
|
end
|
|
214
249
|
|
|
215
250
|
test 'authenticated admin should be able to send an admin invitation' do
|
|
216
|
-
|
|
251
|
+
admin = Admin.create(:email => 'admin@test.com', :password => '123456', :password_confirmation => '123456')
|
|
252
|
+
sign_in_as_user admin
|
|
217
253
|
|
|
218
254
|
send_invitation new_admin_path
|
|
219
|
-
assert_equal
|
|
255
|
+
assert_equal edit_admin_registration_path(admin), current_path
|
|
256
|
+
assert page.has_css?('p#notice', :text => 'An invitation email has been sent to user@test.com.')
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
test 'authenticated admin should be redirected to own page after send a free invitation' do
|
|
260
|
+
admin = Admin.create(:email => 'admin@test.com', :password => '123456', :password_confirmation => '123456')
|
|
261
|
+
sign_in_as_user admin
|
|
262
|
+
|
|
263
|
+
send_invitation new_free_invitation_path
|
|
264
|
+
assert_equal edit_admin_registration_path(admin), current_path
|
|
220
265
|
assert page.has_css?('p#notice', :text => 'An invitation email has been sent to user@test.com.')
|
|
221
266
|
end
|
|
222
267
|
end
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
class
|
|
1
|
+
class ActionDispatch::IntegrationTest
|
|
2
2
|
|
|
3
3
|
def warden
|
|
4
4
|
request.env['warden']
|
|
5
5
|
end
|
|
6
|
-
|
|
6
|
+
|
|
7
7
|
def create_full_user
|
|
8
8
|
@user ||= begin
|
|
9
9
|
user = User.create!(
|
|
@@ -24,7 +24,7 @@ class ActionController::IntegrationTest
|
|
|
24
24
|
visit send("new_#{resource_name}_session_path")
|
|
25
25
|
fill_in "#{resource_name}_email", :with => user.email
|
|
26
26
|
fill_in "#{resource_name}_password", :with => user.password
|
|
27
|
-
click_button '
|
|
27
|
+
click_button 'Log in'
|
|
28
28
|
end
|
|
29
29
|
|
|
30
30
|
# Fix assert_redirect_to in integration sessions because they don't take into
|
|
@@ -28,14 +28,40 @@ class InvitableTest < ActiveSupport::TestCase
|
|
|
28
28
|
test 'should regenerate invitation token each time' do
|
|
29
29
|
user = new_user
|
|
30
30
|
user.invite!
|
|
31
|
-
|
|
31
|
+
|
|
32
32
|
assert_not_nil user.invitation_token
|
|
33
|
+
assert_not_nil user.raw_invitation_token
|
|
33
34
|
assert_not_nil user.invitation_created_at
|
|
35
|
+
|
|
34
36
|
3.times do
|
|
35
37
|
user = User.find(user.id)
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
38
|
+
|
|
39
|
+
assert_not_same user.invitation_token, lambda {
|
|
40
|
+
user.invite!
|
|
41
|
+
user.invitation_token
|
|
42
|
+
}.call
|
|
43
|
+
assert_not_nil user.raw_invitation_token
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
test 'should regenerate invitation token each time even if "skip_invitation" was true' do
|
|
48
|
+
user = new_user
|
|
49
|
+
user.skip_invitation = true
|
|
50
|
+
user.invite!
|
|
51
|
+
|
|
52
|
+
assert_not_nil user.invitation_token
|
|
53
|
+
assert_not_nil user.invitation_created_at
|
|
54
|
+
|
|
55
|
+
3.times do
|
|
56
|
+
user = User.find(user.id)
|
|
57
|
+
user.skip_invitation = true
|
|
58
|
+
|
|
59
|
+
assert_not_same user.invitation_token, lambda {
|
|
60
|
+
user.invite!
|
|
61
|
+
user.invitation_token
|
|
62
|
+
}.call
|
|
63
|
+
assert_not_nil user.invitation_token
|
|
64
|
+
assert_not_nil user.raw_invitation_token
|
|
39
65
|
end
|
|
40
66
|
end
|
|
41
67
|
|
|
@@ -143,7 +169,7 @@ class InvitableTest < ActiveSupport::TestCase
|
|
|
143
169
|
end
|
|
144
170
|
|
|
145
171
|
test 'should set password and password confirmation from params' do
|
|
146
|
-
|
|
172
|
+
User.invite!(:email => "valid@email.com")
|
|
147
173
|
user = User.accept_invitation!(:invitation_token => Thread.current[:token], :password => '123456789', :password_confirmation => '123456789')
|
|
148
174
|
assert user.valid_password?('123456789')
|
|
149
175
|
end
|
|
@@ -157,21 +183,21 @@ class InvitableTest < ActiveSupport::TestCase
|
|
|
157
183
|
|
|
158
184
|
test 'should clear invitation token and set invitation_accepted_at while accepting the password' do
|
|
159
185
|
user = User.invite!(:email => "valid@email.com")
|
|
160
|
-
|
|
186
|
+
assert user.invitation_token.present?
|
|
161
187
|
assert_nil user.invitation_accepted_at
|
|
162
188
|
user.accept_invitation!
|
|
163
189
|
user.reload
|
|
164
190
|
assert_nil user.invitation_token
|
|
165
|
-
|
|
191
|
+
assert user.invitation_accepted_at.present?
|
|
166
192
|
end
|
|
167
193
|
|
|
168
194
|
test 'should not clear invitation token or set accepted_at if record is invalid' do
|
|
169
195
|
user = User.invite!(:email => "valid@email.com")
|
|
170
|
-
|
|
196
|
+
assert user.invitation_token.present?
|
|
171
197
|
assert_nil user.invitation_accepted_at
|
|
172
198
|
User.accept_invitation!(:invitation_token => user.invitation_token, :password => '123456789', :password_confirmation => '987654321')
|
|
173
199
|
user.reload
|
|
174
|
-
|
|
200
|
+
assert user.invitation_token.present?
|
|
175
201
|
assert_nil user.invitation_accepted_at
|
|
176
202
|
end
|
|
177
203
|
|
|
@@ -182,8 +208,8 @@ class InvitableTest < ActiveSupport::TestCase
|
|
|
182
208
|
user.reset_password_sent_at = Time.now.utc
|
|
183
209
|
user.save
|
|
184
210
|
|
|
185
|
-
|
|
186
|
-
|
|
211
|
+
assert user.reset_password_token.present?
|
|
212
|
+
assert user.invitation_token.present?
|
|
187
213
|
User.reset_password_by_token(:reset_password_token => token, :password => '123456789', :password_confirmation => '123456789')
|
|
188
214
|
assert_nil user.reload.reset_password_token
|
|
189
215
|
assert_nil user.reload.invitation_token
|
|
@@ -197,11 +223,11 @@ class InvitableTest < ActiveSupport::TestCase
|
|
|
197
223
|
user.reset_password_sent_at = Time.now.utc
|
|
198
224
|
user.save
|
|
199
225
|
|
|
200
|
-
|
|
201
|
-
|
|
226
|
+
assert user.reset_password_token.present?
|
|
227
|
+
assert user.invitation_token.present?
|
|
202
228
|
User.reset_password_by_token(:reset_password_token => token, :password => '123456789', :password_confirmation => '12345678')
|
|
203
|
-
|
|
204
|
-
|
|
229
|
+
assert user.reload.reset_password_token.present?
|
|
230
|
+
assert user.reload.invitation_token.present?
|
|
205
231
|
assert user.invited_to_sign_up?
|
|
206
232
|
end
|
|
207
233
|
|
|
@@ -212,7 +238,7 @@ class InvitableTest < ActiveSupport::TestCase
|
|
|
212
238
|
user.reset_password_sent_at = Time.now.utc
|
|
213
239
|
user.save
|
|
214
240
|
|
|
215
|
-
|
|
241
|
+
assert user.reset_password_token.present?
|
|
216
242
|
assert_nil user.invitation_token
|
|
217
243
|
User.reset_password_by_token(:reset_password_token => token, :password => '123456789', :password_confirmation => '123456789')
|
|
218
244
|
assert_nil user.reload.invitation_token
|
|
@@ -231,7 +257,7 @@ class InvitableTest < ActiveSupport::TestCase
|
|
|
231
257
|
test 'should return a record with invitation token and no errors to send invitation by email' do
|
|
232
258
|
invited_user = User.invite!(:email => "valid@email.com")
|
|
233
259
|
assert invited_user.errors.blank?
|
|
234
|
-
|
|
260
|
+
assert invited_user.invitation_token.present?
|
|
235
261
|
assert_equal 'valid@email.com', invited_user.email
|
|
236
262
|
assert invited_user.persisted?
|
|
237
263
|
end
|
|
@@ -380,7 +406,7 @@ class InvitableTest < ActiveSupport::TestCase
|
|
|
380
406
|
user = new_user(:password => nil, :password_confirmation => nil)
|
|
381
407
|
user.invite!
|
|
382
408
|
|
|
383
|
-
|
|
409
|
+
User.accept_invitation!(
|
|
384
410
|
:invitation_token => Thread.current[:token],
|
|
385
411
|
:password => 'new_password',
|
|
386
412
|
:password_confirmation => 'new_password'
|
|
@@ -405,6 +431,21 @@ class InvitableTest < ActiveSupport::TestCase
|
|
|
405
431
|
user.reload
|
|
406
432
|
assert !user.valid_password?('new_password')
|
|
407
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
|
+
|
|
408
449
|
|
|
409
450
|
test 'should set other attributes on accepting invitation' do
|
|
410
451
|
user = new_user(:password => nil, :password_confirmation => nil)
|
|
@@ -454,13 +495,13 @@ class InvitableTest < ActiveSupport::TestCase
|
|
|
454
495
|
|
|
455
496
|
test 'should not send an invitation if we want to skip the invitation' do
|
|
456
497
|
assert_no_difference('ActionMailer::Base.deliveries.size') do
|
|
457
|
-
|
|
498
|
+
User.invite!(:email => "valid@email.com", :username => "a"*50, :skip_invitation => true)
|
|
458
499
|
end
|
|
459
500
|
end
|
|
460
501
|
|
|
461
502
|
test 'should not send an invitation if we want to skip the invitation with block' do
|
|
462
503
|
assert_no_difference('ActionMailer::Base.deliveries.size') do
|
|
463
|
-
|
|
504
|
+
User.invite!(:email => "valid@email.com", :username => "a"*50) do |u|
|
|
464
505
|
u.skip_invitation = true
|
|
465
506
|
end
|
|
466
507
|
end
|
|
@@ -472,7 +513,18 @@ class InvitableTest < ActiveSupport::TestCase
|
|
|
472
513
|
assert_no_difference('ActionMailer::Base.deliveries.size') do
|
|
473
514
|
user.invite!
|
|
474
515
|
end
|
|
475
|
-
|
|
516
|
+
assert user.invitation_created_at.present?
|
|
517
|
+
assert_nil user.invitation_sent_at
|
|
518
|
+
end
|
|
519
|
+
|
|
520
|
+
test 'user.invite! should not send an invitation if we want to skip the invitation with block' do
|
|
521
|
+
user = new_user
|
|
522
|
+
assert_no_difference('ActionMailer::Base.deliveries.size') do
|
|
523
|
+
user.invite! do |u|
|
|
524
|
+
u.skip_invitation = true
|
|
525
|
+
end
|
|
526
|
+
end
|
|
527
|
+
assert user.invitation_created_at.present?
|
|
476
528
|
assert_nil user.invitation_sent_at
|
|
477
529
|
end
|
|
478
530
|
|
|
@@ -569,24 +621,28 @@ class InvitableTest < ActiveSupport::TestCase
|
|
|
569
621
|
end
|
|
570
622
|
|
|
571
623
|
test 'should return instance with errors if invitation_token is nil' do
|
|
572
|
-
|
|
624
|
+
User.create(:email => 'admin@test.com', :password => '123456', :password_confirmation => '123456')
|
|
573
625
|
user = User.accept_invitation!
|
|
574
626
|
assert !user.errors.empty?
|
|
575
627
|
end
|
|
576
628
|
|
|
577
|
-
test "should count accepted and not accepted invitations" do
|
|
629
|
+
test "should count invited, created_by_invite, accepted and not accepted invitations" do
|
|
578
630
|
assert_equal 0, User.invitation_not_accepted.count
|
|
579
631
|
assert_equal 0, User.invitation_accepted.count
|
|
632
|
+
assert_equal 0, User.created_by_invite.count
|
|
580
633
|
|
|
581
634
|
User.invite!(:email => "invalid@email.com")
|
|
635
|
+
User.invite!(:email => "another_invalid@email.com")
|
|
582
636
|
user = User.invite!(:email => "valid@email.com")
|
|
583
637
|
|
|
584
|
-
assert_equal
|
|
638
|
+
assert_equal 3, User.invitation_not_accepted.count
|
|
585
639
|
assert_equal 0, User.invitation_accepted.count
|
|
640
|
+
assert_equal 3, User.created_by_invite.count
|
|
586
641
|
|
|
587
642
|
user.accept_invitation!
|
|
588
|
-
assert_equal
|
|
643
|
+
assert_equal 2, User.invitation_not_accepted.count
|
|
589
644
|
assert_equal 1, User.invitation_accepted.count
|
|
645
|
+
assert_equal 3, User.created_by_invite.count
|
|
590
646
|
end
|
|
591
647
|
|
|
592
648
|
test "should preserve return values of Devise::Recoverable#reset_password!" do
|
|
@@ -3,6 +3,13 @@ class ApplicationController < ActionController::Base
|
|
|
3
3
|
before_filter :configure_permitted_parameters, :if => :devise_controller?
|
|
4
4
|
|
|
5
5
|
protected
|
|
6
|
+
def after_sign_in_path_for(resource)
|
|
7
|
+
if resource.is_a? Admin
|
|
8
|
+
edit_admin_registration_path(resource)
|
|
9
|
+
else
|
|
10
|
+
super
|
|
11
|
+
end
|
|
12
|
+
end
|
|
6
13
|
|
|
7
14
|
def configure_permitted_parameters
|
|
8
15
|
devise_parameter_sanitizer.for(:sign_up) { |u| u.permit(:email, :password, :bio) } if defined?(ActionController::StrongParameters)
|
|
@@ -3,4 +3,10 @@ class FreeInvitationsController < Devise::InvitationsController
|
|
|
3
3
|
def authenticate_inviter!
|
|
4
4
|
# everyone can invite
|
|
5
5
|
end
|
|
6
|
+
def current_inviter
|
|
7
|
+
current_admin || current_user
|
|
8
|
+
end
|
|
9
|
+
def after_invite_path_for(resource)
|
|
10
|
+
resource ? super : root_path
|
|
11
|
+
end
|
|
6
12
|
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<h2>Log in</h2>
|
|
2
|
+
|
|
3
|
+
<%= form_for(resource, as: resource_name, url: session_path(resource_name)) do |f| %>
|
|
4
|
+
<div><%= f.label :email %><br />
|
|
5
|
+
<%= f.email_field :email, autofocus: true %></div>
|
|
6
|
+
|
|
7
|
+
<div><%= f.label :password %><br />
|
|
8
|
+
<%= f.password_field :password, autocomplete: "off" %></div>
|
|
9
|
+
|
|
10
|
+
<% if devise_mapping.rememberable? -%>
|
|
11
|
+
<div><%= f.check_box :remember_me %> <%= f.label :remember_me %></div>
|
|
12
|
+
<% end -%>
|
|
13
|
+
|
|
14
|
+
<div><%= f.submit "Log in" %></div>
|
|
15
|
+
<% end %>
|
|
16
|
+
|
|
17
|
+
<%= render "devise/shared/links" %>
|
|
@@ -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
|
+
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:
|
|
11
|
+
date: 2015-06-03 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: bundler
|
|
@@ -66,6 +66,9 @@ executables: []
|
|
|
66
66
|
extensions: []
|
|
67
67
|
extra_rdoc_files: []
|
|
68
68
|
files:
|
|
69
|
+
- CHANGELOG
|
|
70
|
+
- LICENSE
|
|
71
|
+
- README.rdoc
|
|
69
72
|
- app/controllers/devise/invitations_controller.rb
|
|
70
73
|
- app/controllers/devise_invitable/registrations_controller.rb
|
|
71
74
|
- app/views/devise/invitations/edit.html.erb
|
|
@@ -90,9 +93,6 @@ files:
|
|
|
90
93
|
- lib/generators/devise_invitable/templates/simple_form_for/invitations/new.html.erb
|
|
91
94
|
- lib/generators/devise_invitable/views_generator.rb
|
|
92
95
|
- lib/generators/mongoid/devise_invitable_generator.rb
|
|
93
|
-
- LICENSE
|
|
94
|
-
- README.rdoc
|
|
95
|
-
- CHANGELOG
|
|
96
96
|
- test/functional/controller_helpers_test.rb
|
|
97
97
|
- test/functional/registrations_controller_test.rb
|
|
98
98
|
- test/generators/views_generator_test.rb
|
|
@@ -117,6 +117,7 @@ files:
|
|
|
117
117
|
- test/rails_app/app/models/octopussy.rb
|
|
118
118
|
- test/rails_app/app/models/user.rb
|
|
119
119
|
- test/rails_app/app/views/admins/new.html.erb
|
|
120
|
+
- test/rails_app/app/views/devise/sessions/new.html.erb
|
|
120
121
|
- test/rails_app/app/views/free_invitations/new.html.erb
|
|
121
122
|
- test/rails_app/app/views/home/index.html.erb
|
|
122
123
|
- test/rails_app/app/views/layouts/application.html.erb
|
|
@@ -167,7 +168,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
167
168
|
version: 1.3.6
|
|
168
169
|
requirements: []
|
|
169
170
|
rubyforge_project:
|
|
170
|
-
rubygems_version: 2.
|
|
171
|
+
rubygems_version: 2.4.5
|
|
171
172
|
signing_key:
|
|
172
173
|
specification_version: 4
|
|
173
174
|
summary: An invitation strategy for Devise
|
|
@@ -196,6 +197,7 @@ test_files:
|
|
|
196
197
|
- test/rails_app/app/models/octopussy.rb
|
|
197
198
|
- test/rails_app/app/models/user.rb
|
|
198
199
|
- test/rails_app/app/views/admins/new.html.erb
|
|
200
|
+
- test/rails_app/app/views/devise/sessions/new.html.erb
|
|
199
201
|
- test/rails_app/app/views/free_invitations/new.html.erb
|
|
200
202
|
- test/rails_app/app/views/home/index.html.erb
|
|
201
203
|
- test/rails_app/app/views/layouts/application.html.erb
|