FEATURE: Activate users invited via email when invite is redeemed

Do not send an activation email to users invited via email. They
already confirmed their email address by clicking the invite link.
Users invited via link will need to confirm their email address before
they can login.
This commit is contained in:
Gerhard Schlager 2018-12-10 23:24:02 +01:00
parent 73d2fc194d
commit 7977b09025
8 changed files with 121 additions and 34 deletions

View File

@ -211,12 +211,21 @@ class InvitesController < ApplicationController
def post_process_invite(user) def post_process_invite(user)
user.enqueue_welcome_message('welcome_invite') if user.send_welcome_message user.enqueue_welcome_message('welcome_invite') if user.send_welcome_message
if user.has_password? if user.has_password?
email_token = user.email_tokens.create(email: user.email) send_activation_email(user) unless user.active
Jobs.enqueue(:critical_user_email, type: :signup, user_id: user.id, email_token: email_token.token)
elsif !SiteSetting.enable_sso && SiteSetting.enable_local_logins elsif !SiteSetting.enable_sso && SiteSetting.enable_local_logins
Jobs.enqueue(:invite_password_instructions_email, username: user.username) Jobs.enqueue(:invite_password_instructions_email, username: user.username)
end end
end end
def send_activation_email(user)
email_token = user.email_tokens.create(email: user.email)
Jobs.enqueue(:critical_user_email,
type: :signup,
user_id: user.id,
email_token: email_token.token
)
end
end end

View File

@ -107,10 +107,19 @@ class Invite < ActiveRecord::Base
invite = nil invite = nil
end end
invite.update_columns(created_at: Time.zone.now, updated_at: Time.zone.now) if invite if invite
invite.update_columns(
created_at: Time.zone.now,
updated_at: Time.zone.now,
via_email: invite.via_email && send_email
)
else
create_args = {
invited_by: invited_by,
email: lower_email,
via_email: send_email
}
if !invite
create_args = { invited_by: invited_by, email: lower_email }
create_args[:moderator] = true if opts[:moderator] create_args[:moderator] = true if opts[:moderator]
create_args[:custom_message] = custom_message if custom_message create_args[:custom_message] = custom_message if custom_message
invite = Invite.create!(create_args) invite = Invite.create!(create_args)
@ -257,6 +266,7 @@ end
# invalidated_at :datetime # invalidated_at :datetime
# moderator :boolean default(FALSE), not null # moderator :boolean default(FALSE), not null
# custom_message :text # custom_message :text
# via_email :boolean default(FALSE), not null
# #
# Indexes # Indexes
# #

View File

@ -24,7 +24,7 @@ InviteRedeemer = Struct.new(:invite, :username, :name, :password, :user_custom_f
email: invite.email, email: invite.email,
username: available_username, username: available_username,
name: available_name, name: available_name,
active: true, active: false,
trust_level: SiteSetting.default_invitee_trust_level trust_level: SiteSetting.default_invitee_trust_level
} }
@ -57,6 +57,12 @@ InviteRedeemer = Struct.new(:invite, :username, :name, :password, :user_custom_f
end end
user.save! user.save!
if invite.via_email
user.email_tokens.create(email: user.email)
user.activate
end
User.find(user.id) User.find(user.id)
end end
@ -82,8 +88,9 @@ InviteRedeemer = Struct.new(:invite, :username, :name, :password, :user_custom_f
end end
def mark_invite_redeemed def mark_invite_redeemed
Invite.where(['id = ? AND redeemed_at IS NULL AND created_at >= ?', Invite.where('id = ? AND redeemed_at IS NULL AND created_at >= ?',
invite.id, SiteSetting.invite_expiry_days.days.ago]).update_all('redeemed_at = CURRENT_TIMESTAMP') invite.id, SiteSetting.invite_expiry_days.days.ago)
.update_all('redeemed_at = CURRENT_TIMESTAMP')
end end
def get_invited_user def get_invited_user
@ -119,7 +126,7 @@ InviteRedeemer = Struct.new(:invite, :username, :name, :password, :user_custom_f
end end
def send_welcome_message def send_welcome_message
if Invite.where(['email = ?', invite.email]).update_all(['user_id = ?', invited_user.id]) == 1 if Invite.where('email = ?', invite.email).update_all(['user_id = ?', invited_user.id]) == 1
invited_user.send_welcome_message = true invited_user.send_welcome_message = true
end end
end end

View File

@ -0,0 +1,5 @@
class AddViaEmailToInvites < ActiveRecord::Migration[5.2]
def change
add_column :invites, :via_email, :boolean, default: false, null: false
end
end

View File

@ -1,4 +1,5 @@
Fabricator(:invite) do Fabricator(:invite) do
invited_by(fabricator: :user) invited_by(fabricator: :user)
email 'iceking@ADVENTURETIME.ooo' email 'iceking@ADVENTURETIME.ooo'
via_email true
end end

View File

@ -38,7 +38,7 @@ describe InviteRedeemer do
expect(user.id).to eq(staged_user.id) expect(user.id).to eq(staged_user.id)
expect(user.username).to eq('walter') expect(user.username).to eq('walter')
expect(user.name).to eq('Walter White') expect(user.name).to eq('Walter White')
expect(user.active).to eq(false) expect(user.staged).to eq(false)
expect(user.email).to eq('staged@account.com') expect(user.email).to eq('staged@account.com')
expect(user.approved).to eq(true) expect(user.approved).to eq(true)
end end
@ -99,7 +99,6 @@ describe InviteRedeemer do
end end
it "can set password" do it "can set password" do
inviter = invite.invited_by
user = InviteRedeemer.new(invite, username, name, password).redeem user = InviteRedeemer.new(invite, username, name, password).redeem
expect(user).to have_password expect(user).to have_password
expect(user.confirm_password?(password)).to eq(true) expect(user.confirm_password?(password)).to eq(true)

View File

@ -142,6 +142,27 @@ describe Invite do
expect(invite.topics).to match_array([topic, another_topic]) expect(invite.topics).to match_array([topic, another_topic])
end end
end end
it 'correctly marks invite as sent via email' do
expect(invite.via_email).to eq(true)
Invite.invite_by_email(iceking, inviter, topic)
expect(invite.reload.via_email).to eq(true)
end
it 'does not mark invite as sent via email after generating invite link' do
expect(invite.via_email).to eq(true)
Invite.generate_invite_link(iceking, inviter, topic)
expect(invite.reload.via_email).to eq(false)
Invite.invite_by_email(iceking, inviter, topic)
expect(invite.reload.via_email).to eq(false)
Invite.generate_invite_link(iceking, inviter, topic)
expect(invite.reload.via_email).to eq(false)
end
end end
end end
end end

View File

@ -296,10 +296,12 @@ describe InvitesController do
expect(Jobs::SendSystemMessage.jobs.size).to eq(1) expect(Jobs::SendSystemMessage.jobs.size).to eq(1)
end end
it "sends password reset email if password is not set" do context "without password" do
it "sends password reset email" do
put "/invites/show/#{invite.invite_key}.json" put "/invites/show/#{invite.invite_key}.json"
expect(response.status).to eq(200) expect(response.status).to eq(200)
expect(Jobs::InvitePasswordInstructionsEmail.jobs.size).to eq(1) expect(Jobs::InvitePasswordInstructionsEmail.jobs.size).to eq(1)
expect(Jobs::CriticalUserEmail.jobs.size).to eq(0)
end end
it "does not send password reset email if sso is enabled" do it "does not send password reset email if sso is enabled" do
@ -308,6 +310,7 @@ describe InvitesController do
put "/invites/show/#{invite.invite_key}.json" put "/invites/show/#{invite.invite_key}.json"
expect(response.status).to eq(200) expect(response.status).to eq(200)
expect(Jobs::InvitePasswordInstructionsEmail.jobs.size).to eq(0) expect(Jobs::InvitePasswordInstructionsEmail.jobs.size).to eq(0)
expect(Jobs::CriticalUserEmail.jobs.size).to eq(0)
end end
it "does not send password reset email if local login is disabled" do it "does not send password reset email if local login is disabled" do
@ -315,15 +318,47 @@ describe InvitesController do
put "/invites/show/#{invite.invite_key}.json" put "/invites/show/#{invite.invite_key}.json"
expect(response.status).to eq(200) expect(response.status).to eq(200)
expect(Jobs::InvitePasswordInstructionsEmail.jobs.size).to eq(0) expect(Jobs::InvitePasswordInstructionsEmail.jobs.size).to eq(0)
expect(Jobs::CriticalUserEmail.jobs.size).to eq(0)
end
end end
it 'sends an activation email if password is set' do context "with password" do
context "user was invited via email" do
before { invite.update_column(:via_email, true) }
it "doesn't send an activation email and activates the user" do
put "/invites/show/#{invite.invite_key}.json", params: { password: "verystrongpassword" } put "/invites/show/#{invite.invite_key}.json", params: { password: "verystrongpassword" }
expect(response.status).to eq(200) expect(response.status).to eq(200)
expect(Jobs::InvitePasswordInstructionsEmail.jobs.size).to eq(0)
expect(Jobs::CriticalUserEmail.jobs.size).to eq(0)
invited_user = User.find_by_email(invite.email)
expect(invited_user.active).to eq(true)
expect(invited_user.email_confirmed?).to eq(true)
end
end
context "user was invited via link" do
before { invite.update_column(:via_email, false) }
it "sends an activation email and doesn't activate the user" do
put "/invites/show/#{invite.invite_key}.json", params: { password: "verystrongpassword" }
expect(response.status).to eq(200)
expect(Jobs::InvitePasswordInstructionsEmail.jobs.size).to eq(0) expect(Jobs::InvitePasswordInstructionsEmail.jobs.size).to eq(0)
expect(Jobs::CriticalUserEmail.jobs.size).to eq(1) expect(Jobs::CriticalUserEmail.jobs.size).to eq(1)
expect(Jobs::CriticalUserEmail.jobs.first["args"].first["type"]).to eq("signup")
invited_user = User.find_by_email(invite.email)
expect(invited_user.active).to eq(false)
expect(invited_user.email_confirmed?).to eq(false)
end end
end end
end
end
end end
end end