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