From da825451d03a7f16faf54cf3fecdd8893364b3c3 Mon Sep 17 00:00:00 2001 From: Neil Lalonde Date: Tue, 21 Jan 2014 16:53:46 -0500 Subject: [PATCH] Invite link can't be used to log in after you set a password or sign in with 3rd party --- app/controllers/session_controller.rb | 3 ++ .../users/omniauth_callbacks_controller.rb | 4 +-- app/controllers/users_controller.rb | 5 +++- app/models/invite.rb | 19 ++++++++++-- ...121204628_add_invalidated_at_to_invites.rb | 5 ++++ spec/models/invite_spec.rb | 29 +++++++++++++++++++ 6 files changed, 60 insertions(+), 5 deletions(-) create mode 100644 db/migrate/20140121204628_add_invalidated_at_to_invites.rb diff --git a/app/controllers/session_controller.rb b/app/controllers/session_controller.rb index 1ec32942527..e762af5eca4 100644 --- a/app/controllers/session_controller.rb +++ b/app/controllers/session_controller.rb @@ -27,6 +27,9 @@ class SessionController < ApplicationController return end + # User signed on with username and password, so let's prevent the invite link + # from being used to log in (if one exists). + Invite.invalidate_for_email(user.email) else invalid_credentials return diff --git a/app/controllers/users/omniauth_callbacks_controller.rb b/app/controllers/users/omniauth_callbacks_controller.rb index a4842be3e79..f4d2d824507 100644 --- a/app/controllers/users/omniauth_callbacks_controller.rb +++ b/app/controllers/users/omniauth_callbacks_controller.rb @@ -85,8 +85,8 @@ class Users::OmniauthCallbacksController < ApplicationController # log on any account that is active with forum access if Guardian.new(user).can_access_forum? && user.active log_on_user(user) - # don't carry around old auth info, perhaps move elsewhere - session[:authentication] = nil + Invite.invalidate_for_email(user.email) # invite link can't be used to log in anymore + session[:authentication] = nil # don't carry around old auth info, perhaps move elsewhere @data.authenticated = true else if SiteSetting.must_approve_users? && !user.approved? diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index c49e274ea96..f8e0c5a02bf 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -173,7 +173,10 @@ class UsersController < ApplicationController raise Discourse::InvalidParameters.new(:password) unless params[:password].present? @user.password = params[:password] @user.password_required! - logon_after_password_reset if @user.save + if @user.save + Invite.invalidate_for_email(@user.email) # invite link can't be used to log in anymore + logon_after_password_reset + end end render layout: 'no_js' end diff --git a/app/models/invite.rb b/app/models/invite.rb index 53c0f7c527a..63aa45be4b0 100644 --- a/app/models/invite.rb +++ b/app/models/invite.rb @@ -26,7 +26,8 @@ class Invite < ActiveRecord::Base def user_doesnt_already_exist @email_already_exists = false return if email.blank? - if User.where("email = ?", Email.downcase(email)).exists? + u = User.where("email = ?", Email.downcase(email)).first + if u && u.id != self.user_id @email_already_exists = true errors.add(:email) end @@ -40,8 +41,13 @@ class Invite < ActiveRecord::Base created_at < SiteSetting.invite_expiry_days.days.ago end + # link_valid? indicates whether the invite link can be used to log in to the site + def link_valid? + invalidated_at.nil? + end + def redeem - InviteRedeemer.new(self).redeem unless expired? || destroyed? + InviteRedeemer.new(self).redeem unless expired? || destroyed? || !link_valid? end @@ -100,6 +106,15 @@ class Invite < ActiveRecord::Base rails4? ? all : scoped end end + + def self.invalidate_for_email(email) + i = Invite.where(email: Email.downcase(email)).first + if i + i.invalidated_at = Time.zone.now + i.save + end + i + end end # == Schema Information diff --git a/db/migrate/20140121204628_add_invalidated_at_to_invites.rb b/db/migrate/20140121204628_add_invalidated_at_to_invites.rb new file mode 100644 index 00000000000..7ce133dea58 --- /dev/null +++ b/db/migrate/20140121204628_add_invalidated_at_to_invites.rb @@ -0,0 +1,5 @@ +class AddInvalidatedAtToInvites < ActiveRecord::Migration + def change + add_column :invites, :invalidated_at, :datetime + end +end diff --git a/spec/models/invite_spec.rb b/spec/models/invite_spec.rb index 00ce3fc79ec..9c09d462a86 100644 --- a/spec/models/invite_spec.rb +++ b/spec/models/invite_spec.rb @@ -167,6 +167,11 @@ describe Invite do invite.redeem.should be_blank end + it "won't redeem an invalidated invite" do + invite.invalidated_at = 1.day.ago + invite.redeem.should be_blank + end + context 'invite trust levels' do it "returns the trust level in default_invitee_trust_level" do @@ -350,4 +355,28 @@ describe Invite do expect(invites.first).to eq redeemed_invite end end + + describe '.invalidate_for_email' do + let(:email) { 'invite.me@example.com' } + subject { described_class.invalidate_for_email(email) } + + it 'returns nil if there is no invite for the given email' do + subject.should == nil + end + + it 'sets the matching invite to be invalid' do + invite = Fabricate(:invite, invited_by: Fabricate(:user), user_id: nil, email: email) + subject.should == invite + subject.link_valid?.should == false + subject.should be_valid + end + + it 'sets the matching invite to be invalid without being case-sensitive' do + invite = Fabricate(:invite, invited_by: Fabricate(:user), user_id: nil, email: 'invite.me2@Example.COM') + result = described_class.invalidate_for_email('invite.me2@EXAMPLE.com') + result.should == invite + result.link_valid?.should == false + result.should be_valid + end + end end