From 40e8912395ee2dc0f43c2b2a4504f4984c1f040d Mon Sep 17 00:00:00 2001 From: Martin Brennan Date: Thu, 17 Nov 2022 15:51:58 +1000 Subject: [PATCH] FIX: Invite redemption error if user had already redeemed (#19070) When opening the invite acceptance page when the user was already logged in, we were still showing the Accept Invitation prompt even if the user had already redeemed the invitation and was present in the `InvitedUser` table. This would lead to errors when the user clicked on the button. This commit fixes the issue by hiding the Accept Invitation button and showing an error message instead indicating that the user had already redeemed the invitation. This only applies to multi-use invite links. --- .../discourse/app/controllers/invites-show.js | 3 +++ .../discourse/app/templates/invites/show.hbs | 2 +- app/controllers/invites_controller.rb | 10 ++++++++++ app/models/invite.rb | 6 +++++- config/locales/client.en.yml | 1 - config/locales/server.en.yml | 2 ++ spec/requests/invites_controller_spec.rb | 17 +++++++++++++++++ 7 files changed, 38 insertions(+), 3 deletions(-) diff --git a/app/assets/javascripts/discourse/app/controllers/invites-show.js b/app/assets/javascripts/discourse/app/controllers/invites-show.js index c9ad1157079..ea121fac4bb 100644 --- a/app/assets/javascripts/discourse/app/controllers/invites-show.js +++ b/app/assets/javascripts/discourse/app/controllers/invites-show.js @@ -31,6 +31,9 @@ export default Controller.extend( accountEmail: alias("email"), existingUserId: readOnly("model.existing_user_id"), existingUserCanRedeem: readOnly("model.existing_user_can_redeem"), + existingUserCanRedeemError: readOnly( + "model.existing_user_can_redeem_error" + ), existingUserRedeeming: bool("existingUserId"), hiddenEmail: alias("model.hidden_email"), emailVerifiedByLink: alias("model.email_verified_by_link"), diff --git a/app/assets/javascripts/discourse/app/templates/invites/show.hbs b/app/assets/javascripts/discourse/app/templates/invites/show.hbs index c7d9122c4e8..ac357949956 100644 --- a/app/assets/javascripts/discourse/app/templates/invites/show.hbs +++ b/app/assets/javascripts/discourse/app/templates/invites/show.hbs @@ -135,7 +135,7 @@ {{#if this.existingUserCanRedeem}} {{else}} -
{{i18n "invites.existing_user_cannot_redeem"}}
+
{{this.existingUserCanRedeemError}}
{{/if}} {{/if}} {{/if}} diff --git a/app/controllers/invites_controller.rb b/app/controllers/invites_controller.rb index 5c5275c12e7..9f9eb041901 100644 --- a/app/controllers/invites_controller.rb +++ b/app/controllers/invites_controller.rb @@ -408,6 +408,7 @@ class InvitesController < ApplicationController if current_user info[:existing_user_id] = current_user.id info[:existing_user_can_redeem] = invite.can_be_redeemed_by?(current_user) + info[:existing_user_can_redeem_error] = existing_user_can_redeem_error(invite) info[:email] = current_user.email info[:username] = current_user.username end @@ -493,4 +494,13 @@ class InvitesController < ApplicationController end end end + + def existing_user_can_redeem_error(invite) + return if invite.can_be_redeemed_by?(current_user) + if invite.invited_users.exists?(user: current_user) + I18n.t("invite.existing_user_already_redemeed") + else + I18n.t("invite.existing_user_cannot_redeem") + end + end end diff --git a/app/models/invite.rb b/app/models/invite.rb index 19107001857..48b0efe072f 100644 --- a/app/models/invite.rb +++ b/app/models/invite.rb @@ -90,6 +90,10 @@ class Invite < ActiveRecord::Base !redeemed? && !expired? && !deleted_at? && !destroyed? && link_valid? end + def redeemed_by_user?(redeeming_user) + self.invited_users.exists?(user: redeeming_user) + end + def redeemed? if is_invite_link? redemption_count >= max_redemptions_allowed @@ -109,7 +113,7 @@ class Invite < ActiveRecord::Base def can_be_redeemed_by?(user) return false if !self.redeemable? - return true if self.email.blank? && self.domain.blank? + return false if redeemed_by_user?(user) return true if self.email.present? && email_matches?(user.email) self.domain.present? && domain_matches?(user.email) end diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index c016759f072..ebd5a1519d3 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -2097,7 +2097,6 @@ en: name_label: "Name" password_label: "Password" existing_user_can_redeem: "Redeem your invitation to a topic or group." - existing_user_cannot_redeem: "This invitation cannot be redeemed. Please ask the person who invited you to send you a new invitation." password_reset: continue: "Continue to %{site_name}" diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index 669a4edcedb..71d42ab3d8b 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -250,6 +250,8 @@ en:

Otherwise please Reset Password.

not_found_template_link: |

This invitation to %{site_name} can no longer be redeemed. Please ask the person who invited you to send you a new invitation.

+ existing_user_cannot_redeem: "This invitation cannot be redeemed. Please ask the person who invited you to send you a new invitation." + existing_user_already_redemeed: "You have already redeemed this invite link." user_exists: "There's no need to invite %{email}, they already have an account!" invite_exists: "You already invited %{email}." invalid_email: "%{email} isn't a valid email address." diff --git a/spec/requests/invites_controller_spec.rb b/spec/requests/invites_controller_spec.rb index 8410bd66ef5..cebeeab6477 100644 --- a/spec/requests/invites_controller_spec.rb +++ b/spec/requests/invites_controller_spec.rb @@ -126,6 +126,7 @@ RSpec.describe InvitesController do json = JSON.parse(element.current_scope.attribute('data-preloaded').value) invite_info = JSON.parse(json['invite_info']) expect(invite_info['existing_user_can_redeem']).to eq(false) + expect(invite_info['existing_user_can_redeem_error']).to eq(I18n.t("invite.existing_user_cannot_redeem")) end end @@ -141,6 +142,22 @@ RSpec.describe InvitesController do expect(invite_info['existing_user_can_redeem']).to eq(false) end end + + it "does not allow the user to accept the invite when a multi-use invite link has already been redeemed by the user" do + invite.update!(email: nil, max_redemptions_allowed: 10) + expect(invite.redeem(redeeming_user: user)).not_to eq(nil) + + get "/invites/#{invite.invite_key}" + expect(response.status).to eq(200) + + expect(response.body).to have_tag('div#data-preloaded') do |element| + json = JSON.parse(element.current_scope.attribute('data-preloaded').value) + invite_info = JSON.parse(json['invite_info']) + expect(invite_info['existing_user_id']).to eq(user.id) + expect(invite_info['existing_user_can_redeem']).to eq(false) + expect(invite_info['existing_user_can_redeem_error']).to eq(I18n.t("invite.existing_user_already_redemeed")) + end + end end it 'fails if invite does not exist' do