devise_invitable 1.5.5 → 1.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -7
- data/CHANGELOG +21 -0
- data/README.rdoc +32 -14
- data/app/controllers/devise/invitations_controller.rb +19 -7
- data/app/views/devise/invitations/edit.html.erb +2 -0
- data/app/views/devise/mailer/invitation_instructions.text.erb +11 -0
- data/config/locales/en.yml +1 -1
- data/lib/devise_invitable/controllers/helpers.rb +2 -2
- data/lib/devise_invitable/mapping.rb +3 -7
- data/lib/devise_invitable/model.rb +55 -20
- data/lib/devise_invitable/parameter_sanitizer.rb +27 -19
- data/lib/devise_invitable/rails.rb +2 -2
- data/lib/devise_invitable/version.rb +1 -1
- data/lib/devise_invitable.rb +7 -1
- data/lib/generators/devise_invitable/install_generator.rb +2 -2
- data/test/functional/controller_helpers_test.rb +11 -6
- data/test/mailers/invitation_mail_test.rb +20 -6
- data/test/models/invitable_test.rb +102 -67
- data/test/rails_app/app/models/user.rb +8 -3
- data/test/rails_app/config/application.rb +5 -1
- data/test/rails_app/config/database.yml +2 -1
- data/test/rails_app/config/environments/test.rb +6 -0
- data/test/rails_app/config/initializers/devise.rb +1 -1
- data/test/rails_app/db/migrate/20100401102949_create_tables.rb +1 -1
- data/test/test_helper.rb +14 -4
- metadata +51 -46
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
---
|
|
2
|
-
SHA1:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
5
|
-
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
1
|
+
---
|
|
2
|
+
SHA1:
|
|
3
|
+
metadata.gz: babfb02e4177cd88fe44772401f61014f8b462ea
|
|
4
|
+
data.tar.gz: 1302cfd7ca394b79ed9f250fe49e9753c4ea0ac8
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: ec68260bfce3d59a7365e60e14d756815b85f8c65260cc5df7f74cdd5cc0cf443176a4ec8468e91c667150d88e7676bf5acbea6a5c7b30211e2dc76d5d6d9d56
|
|
7
|
+
data.tar.gz: d4213cdfc200c4ba8fd5fee2eb0251cf2b892b6663caa0a9a7e307d62f2819307fcb00f43193d92815e8196277208994471f2a329c95931766cb13e5ef6f2cdb
|
data/CHANGELOG
CHANGED
|
@@ -1,3 +1,24 @@
|
|
|
1
|
+
= 1.6.1
|
|
2
|
+
|
|
3
|
+
- Support 2 arguments on after_invite_path_for, inviter and invitee
|
|
4
|
+
- Support mongoid 6.0 (use :optional on invited_by relation)
|
|
5
|
+
- Support devise 4.1
|
|
6
|
+
|
|
7
|
+
= 1.6.0
|
|
8
|
+
|
|
9
|
+
- Support devise 4.0 and rails 5.0
|
|
10
|
+
- Add before/after invitation_created callbacks
|
|
11
|
+
- Fix invitation_due_at when invite_for is 0
|
|
12
|
+
- Add plain text mailer template
|
|
13
|
+
- Ruby 1.9 not supported anymore
|
|
14
|
+
- Adds :require_password_on_accepting config option, and ensure invitation is not accepted if password is required and removed from form
|
|
15
|
+
|
|
16
|
+
= 1.5.5
|
|
17
|
+
|
|
18
|
+
- Add optional options hash to invite! methods, they will be used for send_devise_notification call
|
|
19
|
+
|
|
20
|
+
= 1.5.4
|
|
21
|
+
|
|
1
22
|
- Ensure that all invited user passwords conform to a format
|
|
2
23
|
- Call set_minimum_password_length (if exists) on accept invitation as devise does
|
|
3
24
|
- Controllers inheriting from Devise::InvitationsController will now use 'devise.invitations' translations
|
data/README.rdoc
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
= DeviseInvitable
|
|
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]
|
|
2
|
+
{<img src="https://badge.fury.io/rb/devise_invitable.svg"/>}[http://badge.fury.io/rb/devise_invitable] {<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
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
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
|
+
It works with Devise >= 3.2
|
|
9
|
+
If you want to use devise 3.0.x, you must use 1.2.1 or lower
|
|
10
|
+
If you want to use devise 3.1.x, you must use 1.3.2 or lower
|
|
9
11
|
|
|
10
12
|
== Installation
|
|
11
13
|
|
|
@@ -16,7 +18,7 @@ Install DeviseInvitable gem, it will also install dependencies (such as devise a
|
|
|
16
18
|
Add DeviseInvitable to your Gemfile (and Devise if you weren't using them):
|
|
17
19
|
|
|
18
20
|
gem 'devise', '~> 3.5.2'
|
|
19
|
-
gem 'devise_invitable', '~> 1.
|
|
21
|
+
gem 'devise_invitable', '~> 1.6.0'
|
|
20
22
|
|
|
21
23
|
=== Automatic installation
|
|
22
24
|
|
|
@@ -130,12 +132,16 @@ or directly as parameters to the <tt>devise</tt> method:
|
|
|
130
132
|
|
|
131
133
|
* resend_invitation: resend invitation if user with invited status is invited again. Enabled by default.
|
|
132
134
|
|
|
133
|
-
* invited_by_class_name:
|
|
135
|
+
* invited_by_class_name: the class name of the inviting model. If this is nil, polymorphic association is used.
|
|
134
136
|
|
|
135
|
-
* invited_by_foreign_key:
|
|
137
|
+
* invited_by_foreign_key: the foreign key to the inviting model (only used if invited_by_class_name is set, otherwise :invited_by_id)
|
|
138
|
+
|
|
139
|
+
* invited_by_counter_cache: the column name used for counter_cache column. If this is nil (default value), the invited_by association is declared without counter_cache.
|
|
136
140
|
|
|
137
141
|
* allow_insecure_sign_in_after_accept: automatically sign in the user after they set a password. Enabled by default.
|
|
138
142
|
|
|
143
|
+
* require_password_on_accepting: require password when user accepts the invitation. Enabled by default. Disable if you don't want to ask or enforce to set password while accepting, because is set when user is invited or it will be set later.
|
|
144
|
+
|
|
139
145
|
For more details, see <tt>config/initializers/devise.rb</tt> (after you invoked the "devise_invitable:install" generator described above).
|
|
140
146
|
|
|
141
147
|
== Configuring views
|
|
@@ -160,7 +166,7 @@ To change the controller's behavior, create a controller that inherits from <tt>
|
|
|
160
166
|
|
|
161
167
|
class Users::InvitationsController < Devise::InvitationsController
|
|
162
168
|
def update
|
|
163
|
-
if
|
|
169
|
+
if some_condition
|
|
164
170
|
redirect_to root_path
|
|
165
171
|
else
|
|
166
172
|
super
|
|
@@ -174,7 +180,7 @@ Now just tell Devise that you want to use your controller, the controller above
|
|
|
174
180
|
|
|
175
181
|
be sure that you generate the views and put them into the controller that you generated, so for this example it would be:
|
|
176
182
|
|
|
177
|
-
rails generate devise_invitable:views users
|
|
183
|
+
rails generate devise_invitable:views users
|
|
178
184
|
|
|
179
185
|
To change behaviour of inviting or accepting users, you can simply override two methods:
|
|
180
186
|
|
|
@@ -265,7 +271,7 @@ To check if a particular user is created by invitation, irrespective to state of
|
|
|
265
271
|
|
|
266
272
|
**Warning**
|
|
267
273
|
|
|
268
|
-
When using skip_invitation you must send the email with the user object instance that generated the tokens, as
|
|
274
|
+
When using skip_invitation you must send the email with the user object instance that generated the tokens, as
|
|
269
275
|
user.raw_invitation_token is available only to the instance and is not persisted in the database.
|
|
270
276
|
|
|
271
277
|
You can send an invitation to an existing user if your workflow creates them separately:
|
|
@@ -277,6 +283,11 @@ You can also set <tt>invited_by</tt> when using the <tt>invite!</tt> class metho
|
|
|
277
283
|
|
|
278
284
|
User.invite!({:email => "new_user@example.com"}, current_user) # current_user will be set as invited_by
|
|
279
285
|
|
|
286
|
+
=== Find by invitation token
|
|
287
|
+
|
|
288
|
+
To find by invitation token use the <tt>find_by_invitation_token</tt> class method.
|
|
289
|
+
|
|
290
|
+
user = User.find_by_invitation_token(params[:invitation_token], true)
|
|
280
291
|
|
|
281
292
|
|
|
282
293
|
=== Accept an invitation
|
|
@@ -287,12 +298,17 @@ To accept an invitation with a token use the <tt>accept_invitation!</tt> class m
|
|
|
287
298
|
|
|
288
299
|
=== Callbacks
|
|
289
300
|
|
|
290
|
-
A callback event is fired before and after an invitation is accepted (User#accept_invitation!). For example, in your resource model you can add:
|
|
301
|
+
A callback event is fired before and after an invitation is created (User#invite!) or accepted (User#accept_invitation!). For example, in your resource model you can add:
|
|
291
302
|
|
|
303
|
+
before_invitation_created :email_admins
|
|
292
304
|
after_invitation_accepted :email_invited_by
|
|
293
305
|
|
|
306
|
+
def email_admins
|
|
307
|
+
# ...
|
|
308
|
+
end
|
|
309
|
+
|
|
294
310
|
def email_invited_by
|
|
295
|
-
|
|
311
|
+
# ...
|
|
296
312
|
end
|
|
297
313
|
|
|
298
314
|
The callbacks support all options and arguments available to the standard callbacks provided by AR.
|
|
@@ -303,14 +319,14 @@ A pair of scopes to find those users that have accepted, and those that have not
|
|
|
303
319
|
|
|
304
320
|
User.invitation_accepted # => returns all Users for whom the invitation_accepted_at attribute is not nil
|
|
305
321
|
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
|
|
322
|
+
User.created_by_invite # => returns all Users who are created by invitations, irrespective to invitation status
|
|
307
323
|
|
|
308
324
|
== Integration in a Rails application
|
|
309
325
|
|
|
310
326
|
Since the invitations controller take care of all the creation/acceptation of an invitation, in most cases you wouldn't call the <tt>invite!</tt> and <tt>accept_invitation!</tt> methods directly.
|
|
311
327
|
Instead, in your views, put a link to <tt>new_user_invitation_path</tt> or <tt>new_invitation_path(:user)</tt> or even <tt>/users/invitation/new</tt> to prepare and send an invitation (to a user in this example).
|
|
312
328
|
|
|
313
|
-
After an invitation is created and sent, the inviter will be redirected to after_invite_path_for(
|
|
329
|
+
After an invitation is created and sent, the inviter will be redirected to after_invite_path_for(inviter, invitee), which is stored path or the same path as after_sign_in_path_for by default.
|
|
314
330
|
|
|
315
331
|
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.
|
|
316
332
|
|
|
@@ -386,6 +402,8 @@ The DeviseInvitable mailer uses the same pattern as Devise to create mail subjec
|
|
|
386
402
|
|
|
387
403
|
Take a look at the generated locale file (in <tt>config/locales/devise_invitable.en.yml</tt>) to check all available messages.
|
|
388
404
|
|
|
405
|
+
Translations are not included in gem (and they won't), they are available at wiki[https://github.com/scambra/devise_invitable/wiki/I18n].
|
|
406
|
+
|
|
389
407
|
== Other ORMs
|
|
390
408
|
|
|
391
409
|
DeviseInvitable supports ActiveRecord and Mongoid, like Devise.
|
|
@@ -398,9 +416,9 @@ https://github.com/scambra/devise_invitable/wiki
|
|
|
398
416
|
|
|
399
417
|
== Testing
|
|
400
418
|
|
|
401
|
-
To test DeviseInvitable for the ActiveRecord ORM with RVM, Ruby
|
|
419
|
+
To test DeviseInvitable for the ActiveRecord ORM with RVM, Ruby 2.0.0:
|
|
402
420
|
|
|
403
|
-
rvm use
|
|
421
|
+
rvm use 2.0.0
|
|
404
422
|
rvm gemset create devise_invitable
|
|
405
423
|
rvm gemset use devise_invitable
|
|
406
424
|
gem install bundler
|
|
@@ -1,10 +1,19 @@
|
|
|
1
1
|
class Devise::InvitationsController < DeviseController
|
|
2
|
+
if Rails::VERSION::MAJOR >= 5
|
|
3
|
+
prepend_before_action :authenticate_inviter!, :only => [:new, :create]
|
|
4
|
+
prepend_before_action :has_invitations_left?, :only => [:create]
|
|
5
|
+
prepend_before_action :require_no_authentication, :only => [:edit, :update, :destroy]
|
|
6
|
+
prepend_before_action :resource_from_invitation_token, :only => [:edit, :destroy]
|
|
7
|
+
else
|
|
8
|
+
prepend_before_filter :authenticate_inviter!, :only => [:new, :create]
|
|
9
|
+
prepend_before_filter :has_invitations_left?, :only => [:create]
|
|
10
|
+
prepend_before_filter :require_no_authentication, :only => [:edit, :update, :destroy]
|
|
11
|
+
prepend_before_filter :resource_from_invitation_token, :only => [:edit, :destroy]
|
|
12
|
+
end
|
|
2
13
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
prepend_before_filter :resource_from_invitation_token, :only => [:edit, :destroy]
|
|
7
|
-
helper_method :after_sign_in_path_for
|
|
14
|
+
if respond_to? :helper_method
|
|
15
|
+
helper_method :after_sign_in_path_for
|
|
16
|
+
end
|
|
8
17
|
|
|
9
18
|
# GET /resource/invitation/new
|
|
10
19
|
def new
|
|
@@ -23,7 +32,11 @@ class Devise::InvitationsController < DeviseController
|
|
|
23
32
|
if is_flashing_format? && self.resource.invitation_sent_at
|
|
24
33
|
set_flash_message :notice, :send_instructions, :email => self.resource.email
|
|
25
34
|
end
|
|
26
|
-
|
|
35
|
+
if self.method(:after_invite_path_for).arity == 1
|
|
36
|
+
respond_with resource, :location => after_invite_path_for(current_inviter)
|
|
37
|
+
else
|
|
38
|
+
respond_with resource, :location => after_invite_path_for(current_inviter, resource)
|
|
39
|
+
end
|
|
27
40
|
else
|
|
28
41
|
respond_with_navigational(resource) { render :new }
|
|
29
42
|
end
|
|
@@ -108,4 +121,3 @@ class Devise::InvitationsController < DeviseController
|
|
|
108
121
|
'devise.invitations'
|
|
109
122
|
end
|
|
110
123
|
end
|
|
111
|
-
|
|
@@ -4,11 +4,13 @@
|
|
|
4
4
|
<%= devise_error_messages! %>
|
|
5
5
|
<%= f.hidden_field :invitation_token %>
|
|
6
6
|
|
|
7
|
+
<% if f.object.class.require_password_on_accepting %>
|
|
7
8
|
<p><%= f.label :password %><br />
|
|
8
9
|
<%= f.password_field :password %></p>
|
|
9
10
|
|
|
10
11
|
<p><%= f.label :password_confirmation %><br />
|
|
11
12
|
<%= f.password_field :password_confirmation %></p>
|
|
13
|
+
<% end %>
|
|
12
14
|
|
|
13
15
|
<p><%= f.submit t("devise.invitations.edit.submit_button") %></p>
|
|
14
16
|
<% end %>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
<%= t("devise.mailer.invitation_instructions.hello", email: @resource.email) %>
|
|
2
|
+
|
|
3
|
+
<%= t("devise.mailer.invitation_instructions.someone_invited_you", url: root_url) %>
|
|
4
|
+
|
|
5
|
+
<%= accept_invitation_url(@resource, :invitation_token => @token) %>
|
|
6
|
+
|
|
7
|
+
<% if @resource.invitation_due_at %>
|
|
8
|
+
<%= t("devise.mailer.invitation_instructions.accept_until", due_date: l(@resource.invitation_due_at, format: :'devise.mailer.invitation_instructions.accept_until_format')) %>
|
|
9
|
+
<% end %>
|
|
10
|
+
|
|
11
|
+
<%= strip_tags t("devise.mailer.invitation_instructions.ignore") %>
|
data/config/locales/en.yml
CHANGED
|
@@ -22,7 +22,7 @@ en:
|
|
|
22
22
|
someone_invited_you: "Someone has invited you to %{url}, you can accept it through the link below."
|
|
23
23
|
accept: "Accept invitation"
|
|
24
24
|
accept_until: "This invitation will be due in %{due_date}."
|
|
25
|
-
ignore: "If you don't want to accept the invitation, please ignore this email.<br
|
|
25
|
+
ignore: "If you don't want to accept the invitation, please ignore this email.<br />\nYour account won't be created until you access the link above and set your password."
|
|
26
26
|
time:
|
|
27
27
|
formats:
|
|
28
28
|
devise:
|
|
@@ -4,8 +4,8 @@ module DeviseInvitable::Controllers::Helpers
|
|
|
4
4
|
included do
|
|
5
5
|
end
|
|
6
6
|
|
|
7
|
-
def after_invite_path_for(
|
|
8
|
-
after_sign_in_path_for(
|
|
7
|
+
def after_invite_path_for(inviter, invitee = nil)
|
|
8
|
+
after_sign_in_path_for(inviter)
|
|
9
9
|
end
|
|
10
10
|
|
|
11
11
|
def after_accept_path_for(resource)
|
|
@@ -1,14 +1,10 @@
|
|
|
1
1
|
module DeviseInvitable
|
|
2
2
|
module Mapping
|
|
3
|
-
def self.included(base)
|
|
4
|
-
base.alias_method_chain :default_controllers, :invitable
|
|
5
|
-
end
|
|
6
|
-
|
|
7
3
|
private
|
|
8
|
-
def
|
|
4
|
+
def default_controllers(options)
|
|
9
5
|
options[:controllers] ||= {}
|
|
10
6
|
options[:controllers][:registrations] ||= "devise_invitable/registrations"
|
|
11
|
-
|
|
7
|
+
super
|
|
12
8
|
end
|
|
13
9
|
end
|
|
14
|
-
end
|
|
10
|
+
end
|
|
@@ -40,11 +40,15 @@ module Devise
|
|
|
40
40
|
if defined?(ActiveRecord) && defined?(ActiveRecord::Base) && self < ActiveRecord::Base
|
|
41
41
|
counter_cache = Devise.invited_by_counter_cache
|
|
42
42
|
belongs_to_options.merge! :counter_cache => counter_cache if counter_cache
|
|
43
|
+
belongs_to_options.merge! :optional => true if ActiveRecord::VERSION::MAJOR >= 5
|
|
44
|
+
elsif defined?(Mongoid) && defined?(Mongoid::Document) && self < Mongoid::Document && Mongoid::VERSION >= '6.0.0'
|
|
45
|
+
belongs_to_options.merge! :optional => true
|
|
43
46
|
end
|
|
44
47
|
belongs_to :invited_by, belongs_to_options
|
|
45
48
|
|
|
46
|
-
|
|
47
|
-
|
|
49
|
+
extend ActiveModel::Callbacks
|
|
50
|
+
define_model_callbacks :invitation_created
|
|
51
|
+
define_model_callbacks :invitation_accepted
|
|
48
52
|
|
|
49
53
|
scope :no_active_invitation, lambda { where(:invitation_token => nil) }
|
|
50
54
|
if defined?(Mongoid) && defined?(Mongoid::Document) && self < Mongoid::Document
|
|
@@ -56,7 +60,14 @@ module Devise
|
|
|
56
60
|
scope :invitation_not_accepted, lambda { where(arel_table[:invitation_token].not_eq(nil)).where(:invitation_accepted_at => nil) }
|
|
57
61
|
scope :invitation_accepted, lambda { where(arel_table[:invitation_accepted_at].not_eq(nil)) }
|
|
58
62
|
|
|
59
|
-
|
|
63
|
+
callbacks = [
|
|
64
|
+
:before_invitation_created,
|
|
65
|
+
:after_invitation_created,
|
|
66
|
+
:before_invitation_accepted,
|
|
67
|
+
:after_invitation_accepted,
|
|
68
|
+
]
|
|
69
|
+
|
|
70
|
+
callbacks.each do |callback_method|
|
|
60
71
|
send callback_method
|
|
61
72
|
end
|
|
62
73
|
end
|
|
@@ -79,11 +90,12 @@ module Devise
|
|
|
79
90
|
# Accept an invitation by clearing invitation token and and setting invitation_accepted_at
|
|
80
91
|
# Saves the model and confirms it if model is confirmable, running invitation_accepted callbacks
|
|
81
92
|
def accept_invitation!
|
|
82
|
-
if self.invited_to_sign_up?
|
|
93
|
+
if self.invited_to_sign_up?
|
|
94
|
+
@accepting_invitation = true
|
|
83
95
|
run_callbacks :invitation_accepted do
|
|
84
96
|
self.accept_invitation
|
|
85
97
|
self.confirmed_at = self.invitation_accepted_at if self.respond_to?(:confirmed_at)
|
|
86
|
-
self.save
|
|
98
|
+
self.save
|
|
87
99
|
end
|
|
88
100
|
end
|
|
89
101
|
end
|
|
@@ -95,15 +107,20 @@ module Devise
|
|
|
95
107
|
|
|
96
108
|
# Verifies whether a user has been invited or not
|
|
97
109
|
def invited_to_sign_up?
|
|
98
|
-
persisted? && invitation_token.present?
|
|
110
|
+
accepting_invitation? || (persisted? && invitation_token.present?)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# Returns true if accept_invitation! was called
|
|
114
|
+
def accepting_invitation?
|
|
115
|
+
@accepting_invitation
|
|
99
116
|
end
|
|
100
117
|
|
|
101
|
-
# Verifies whether a user accepted an invitation (
|
|
118
|
+
# Verifies whether a user accepted an invitation (false when user is accepting it)
|
|
102
119
|
def invitation_accepted?
|
|
103
|
-
invitation_accepted_at.present?
|
|
120
|
+
!accepting_invitation? && invitation_accepted_at.present?
|
|
104
121
|
end
|
|
105
122
|
|
|
106
|
-
# Verifies whether a user has accepted an invitation (
|
|
123
|
+
# Verifies whether a user has accepted an invitation (false when user is accepting it), or was never invited
|
|
107
124
|
def accepted_or_not_invited?
|
|
108
125
|
invitation_accepted? || !invited_to_sign_up?
|
|
109
126
|
end
|
|
@@ -121,17 +138,20 @@ module Devise
|
|
|
121
138
|
|
|
122
139
|
yield self if block_given?
|
|
123
140
|
generate_invitation_token if no_token_present_or_skip_invitation?
|
|
124
|
-
self.invitation_created_at = Time.now.utc
|
|
125
|
-
self.invitation_sent_at = self.invitation_created_at unless skip_invitation
|
|
126
|
-
self.invited_by = invited_by if invited_by
|
|
127
141
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
142
|
+
run_callbacks :invitation_created do
|
|
143
|
+
self.invitation_created_at = Time.now.utc
|
|
144
|
+
self.invitation_sent_at = self.invitation_created_at unless skip_invitation
|
|
145
|
+
self.invited_by = invited_by if invited_by
|
|
131
146
|
|
|
132
|
-
|
|
133
|
-
self.
|
|
134
|
-
|
|
147
|
+
# Call these before_validate methods since we aren't validating on save
|
|
148
|
+
self.downcase_keys if new_record_and_responds_to?(:downcase_keys)
|
|
149
|
+
self.strip_whitespace if new_record_and_responds_to?(:strip_whitespace)
|
|
150
|
+
|
|
151
|
+
if save(:validate => false)
|
|
152
|
+
self.invited_by.decrement_invitation_limit! if !was_invited and self.invited_by.present?
|
|
153
|
+
deliver_invitation(options) unless skip_invitation
|
|
154
|
+
end
|
|
135
155
|
end
|
|
136
156
|
end
|
|
137
157
|
|
|
@@ -144,7 +164,12 @@ module Devise
|
|
|
144
164
|
|
|
145
165
|
# Only verify password when is not invited
|
|
146
166
|
def valid_password?(password)
|
|
147
|
-
super unless block_from_invitation?
|
|
167
|
+
super unless !accepting_invitation? && block_from_invitation?
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
# Enforce password when invitation is being accepted
|
|
171
|
+
def password_required?
|
|
172
|
+
(accepting_invitation? && self.class.require_password_on_accepting) || super
|
|
148
173
|
end
|
|
149
174
|
|
|
150
175
|
def unauthenticated_message
|
|
@@ -180,7 +205,8 @@ module Devise
|
|
|
180
205
|
end
|
|
181
206
|
|
|
182
207
|
def invitation_due_at
|
|
183
|
-
return nil
|
|
208
|
+
return nil if (self.class.invite_for == 0 || self.class.invite_for.nil?)
|
|
209
|
+
#return nil unless self.class.invite_for
|
|
184
210
|
|
|
185
211
|
time = self.invitation_created_at || self.invitation_sent_at
|
|
186
212
|
time + self.class.invite_for
|
|
@@ -311,6 +337,14 @@ module Devise
|
|
|
311
337
|
end
|
|
312
338
|
|
|
313
339
|
# Callback convenience methods
|
|
340
|
+
def before_invitation_created(*args, &blk)
|
|
341
|
+
set_callback(:invitation_created, :before, *args, &blk)
|
|
342
|
+
end
|
|
343
|
+
|
|
344
|
+
def after_invitation_created(*args, &blk)
|
|
345
|
+
set_callback(:invitation_created, :after, *args, &blk)
|
|
346
|
+
end
|
|
347
|
+
|
|
314
348
|
def before_invitation_accepted(*args, &blk)
|
|
315
349
|
set_callback(:invitation_accepted, :before, *args, &blk)
|
|
316
350
|
end
|
|
@@ -326,6 +360,7 @@ module Devise
|
|
|
326
360
|
Devise::Models.config(self, :invite_key)
|
|
327
361
|
Devise::Models.config(self, :resend_invitation)
|
|
328
362
|
Devise::Models.config(self, :allow_insecure_sign_in_after_accept)
|
|
363
|
+
Devise::Models.config(self, :require_password_on_accepting)
|
|
329
364
|
|
|
330
365
|
private
|
|
331
366
|
|
|
@@ -1,29 +1,37 @@
|
|
|
1
1
|
module DeviseInvitable
|
|
2
2
|
module ParameterSanitizer
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
def accept_invitation
|
|
8
|
-
permit self.for(:accept_invitation)
|
|
9
|
-
end
|
|
3
|
+
if defined?(Devise::BaseSanitizer)
|
|
4
|
+
def invite
|
|
5
|
+
permit self.for(:invite)
|
|
6
|
+
end
|
|
10
7
|
|
|
11
|
-
|
|
12
|
-
|
|
8
|
+
def accept_invitation
|
|
9
|
+
permit self.for(:accept_invitation)
|
|
10
|
+
end
|
|
13
11
|
end
|
|
14
12
|
|
|
15
13
|
private
|
|
16
|
-
def permit(keys)
|
|
17
|
-
default_params.permit(*Array(keys))
|
|
18
|
-
end
|
|
19
14
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
15
|
+
if defined?(Devise::BaseSanitizer)
|
|
16
|
+
def permit(keys)
|
|
17
|
+
default_params.permit(*Array(keys))
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def attributes_for(kind)
|
|
21
|
+
case kind
|
|
22
|
+
when :invite
|
|
23
|
+
resource_class.respond_to?(:invite_key_fields) ? resource_class.invite_key_fields : []
|
|
24
|
+
when :accept_invitation
|
|
25
|
+
[:password, :password_confirmation, :invitation_token]
|
|
26
|
+
else
|
|
27
|
+
super
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
else
|
|
31
|
+
def initialize(resource_class, resource_name, params)
|
|
32
|
+
super
|
|
33
|
+
permit(:invite, keys: (resource_class.respond_to?(:invite_key_fields) ? resource_class.invite_key_fields : []) )
|
|
34
|
+
permit(:accept_invitation, keys: [:password, :password_confirmation, :invitation_token] )
|
|
27
35
|
end
|
|
28
36
|
end
|
|
29
37
|
end
|
|
@@ -16,8 +16,8 @@ module DeviseInvitable
|
|
|
16
16
|
end
|
|
17
17
|
# extend mapping with after_initialize because it's not reloaded
|
|
18
18
|
config.after_initialize do
|
|
19
|
-
Devise::Mapping.send :
|
|
20
|
-
Devise::ParameterSanitizer.send :
|
|
19
|
+
Devise::Mapping.send :prepend, DeviseInvitable::Mapping
|
|
20
|
+
Devise::ParameterSanitizer.send :prepend, DeviseInvitable::ParameterSanitizer
|
|
21
21
|
end
|
|
22
22
|
end
|
|
23
23
|
end
|
data/lib/devise_invitable.rb
CHANGED
|
@@ -72,10 +72,16 @@ module Devise
|
|
|
72
72
|
mattr_accessor :invited_by_counter_cache
|
|
73
73
|
@@invited_by_counter_cache = nil
|
|
74
74
|
|
|
75
|
-
# Public: Auto-login after the user accepts the
|
|
75
|
+
# Public: Auto-login after the user accepts the invitation. If this is false,
|
|
76
76
|
# the user will need to manually log in after accepting the invite (default: true).
|
|
77
77
|
mattr_accessor :allow_insecure_sign_in_after_accept
|
|
78
78
|
@@allow_insecure_sign_in_after_accept = true
|
|
79
|
+
|
|
80
|
+
# Public: Require password when user accepts the invitation. Set to false if
|
|
81
|
+
# you don't want to ask or enforce to set password while accepting, because is
|
|
82
|
+
# set when user is invited or it will be set later (default: true).
|
|
83
|
+
mattr_accessor :require_password_on_accepting
|
|
84
|
+
@@require_password_on_accepting = true
|
|
79
85
|
end
|
|
80
86
|
|
|
81
87
|
Devise.add_module :invitable, :controller => :invitations, :model => 'devise_invitable/model', :route => {:invitation => [nil, :new, :accept]}
|
|
@@ -63,8 +63,8 @@ module DeviseInvitable
|
|
|
63
63
|
|
|
64
64
|
# Auto-login after the user accepts the invite. If this is false,
|
|
65
65
|
# the user will need to manually log in after accepting the invite.
|
|
66
|
-
# Default:
|
|
67
|
-
# config.allow_insecure_sign_in_after_accept =
|
|
66
|
+
# Default: true
|
|
67
|
+
# config.allow_insecure_sign_in_after_accept = false
|
|
68
68
|
|
|
69
69
|
CONTENT
|
|
70
70
|
end
|
|
@@ -6,32 +6,37 @@ class ControllerHelpersTest < ActionController::TestCase
|
|
|
6
6
|
test "after invite path defaults to after sign in path" do
|
|
7
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
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
|
|
15
15
|
custom_path = 'customized/after/invite/path'
|
|
16
16
|
@controller.instance_eval "def after_invite_path_for(resource) '#{custom_path}' end"
|
|
17
17
|
assert_equal @controller.after_invite_path_for(:user), custom_path
|
|
18
18
|
end
|
|
19
|
-
|
|
19
|
+
|
|
20
|
+
test 'after invite path is customizable from application controller with invited' do
|
|
21
|
+
custom_path = 'customized/after/invite/path'
|
|
22
|
+
@controller.instance_eval "def after_invite_path_for(resource, invited) '#{custom_path}' end"
|
|
23
|
+
assert_equal @controller.after_invite_path_for(:user, :invited), custom_path
|
|
24
|
+
end
|
|
20
25
|
test 'after accept path is customizable from application controller' do
|
|
21
26
|
custom_path = 'customized/after/accept/path'
|
|
22
27
|
@controller.instance_eval "def after_accept_path_for(resource) '#{custom_path}' end"
|
|
23
28
|
assert_equal @controller.after_accept_path_for(:user), custom_path
|
|
24
29
|
end
|
|
25
|
-
|
|
30
|
+
|
|
26
31
|
test 'is not a devise controller' do
|
|
27
32
|
assert !@controller.devise_controller?
|
|
28
33
|
end
|
|
29
|
-
|
|
34
|
+
|
|
30
35
|
test 'invitations controller respects definition for after invite path in application controller' do
|
|
31
36
|
assert Devise::InvitationsController.method_defined? :after_invite_path_for
|
|
32
37
|
assert !Devise::InvitationsController.instance_methods(false).include?(:after_invite_path_for)
|
|
33
38
|
end
|
|
34
|
-
|
|
39
|
+
|
|
35
40
|
test 'invitations controller respects definition for after accept path in application controller' do
|
|
36
41
|
assert Devise::InvitationsController.method_defined? :after_accept_path_for
|
|
37
42
|
assert !Devise::InvitationsController.instance_methods(false).include?(:after_accept_path_for)
|
|
@@ -23,8 +23,8 @@ class InvitationMailTest < ActionMailer::TestCase
|
|
|
23
23
|
assert_not_nil mail
|
|
24
24
|
end
|
|
25
25
|
|
|
26
|
-
test 'content type should be set to
|
|
27
|
-
|
|
26
|
+
test 'content type should be set to multipart' do
|
|
27
|
+
assert_match /^multipart\/alternative; boundary="[^"]+"; charset=UTF-8/, mail.content_type
|
|
28
28
|
end
|
|
29
29
|
|
|
30
30
|
test 'send invitation to the user email' do
|
|
@@ -48,30 +48,44 @@ class InvitationMailTest < ActionMailer::TestCase
|
|
|
48
48
|
end
|
|
49
49
|
|
|
50
50
|
test 'body should have user info' do
|
|
51
|
-
assert_match /#{user.email}/, mail.body.decoded
|
|
51
|
+
assert_match /#{user.email}/, mail.html_part.body.decoded
|
|
52
|
+
assert_match /#{user.email}/, mail.text_part.body.decoded
|
|
52
53
|
end
|
|
53
54
|
|
|
54
55
|
test 'body should have link to confirm the account' do
|
|
55
56
|
host = ActionMailer::Base.default_url_options[:host]
|
|
56
|
-
body = mail.body.decoded
|
|
57
|
+
body = mail.html_part.body.decoded
|
|
57
58
|
invitation_url_regexp = %r{<a href=\"http://#{host}/users/invitation/accept\?invitation_token=#{Thread.current[:token]}">}
|
|
58
59
|
assert_match invitation_url_regexp, body
|
|
60
|
+
|
|
61
|
+
body = mail.text_part.body.decoded
|
|
62
|
+
invitation_url_regexp = %r{http://#{host}/users/invitation/accept\?invitation_token=#{Thread.current[:token]}}
|
|
63
|
+
assert_match invitation_url_regexp, body
|
|
59
64
|
end
|
|
60
65
|
|
|
61
66
|
test 'body should have link to confirm the account on resend' do
|
|
62
67
|
host = ActionMailer::Base.default_url_options[:host]
|
|
63
68
|
user
|
|
64
69
|
@user = User.find(user.id).invite!
|
|
65
|
-
body = mail.body.decoded
|
|
70
|
+
body = mail.html_part.body.decoded
|
|
66
71
|
invitation_url_regexp = %r{<a href=\"http://#{host}/users/invitation/accept\?invitation_token=#{Thread.current[:token]}">}
|
|
67
72
|
assert_match invitation_url_regexp, body
|
|
73
|
+
|
|
74
|
+
body = mail.text_part.body.decoded
|
|
75
|
+
invitation_url_regexp = %r{http://#{host}/users/invitation/accept\?invitation_token=#{Thread.current[:token]}}
|
|
76
|
+
assert_match invitation_url_regexp, body
|
|
68
77
|
end
|
|
69
78
|
|
|
70
79
|
test 'body should have invitation due date when it exists' do
|
|
80
|
+
User.stubs(:invite_for => 5)
|
|
81
|
+
|
|
71
82
|
host = ActionMailer::Base.default_url_options[:host]
|
|
72
83
|
user
|
|
73
|
-
body = mail.body.decoded
|
|
84
|
+
body = mail.html_part.body.decoded
|
|
85
|
+
due_date_regexp = %r{#{I18n.l user.invitation_due_at, format: :'devise.mailer.invitation_instructions.accept_until_format' }}
|
|
86
|
+
assert_match due_date_regexp, body
|
|
74
87
|
|
|
88
|
+
body = mail.text_part.body.decoded
|
|
75
89
|
due_date_regexp = %r{#{I18n.l user.invitation_due_at, format: :'devise.mailer.invitation_instructions.accept_until_format' }}
|
|
76
90
|
assert_match due_date_regexp, body
|
|
77
91
|
end
|