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.
This commit is contained in:
Martin Brennan 2022-11-17 15:51:58 +10:00 committed by GitHub
parent 5ee3e2932f
commit 40e8912395
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 38 additions and 3 deletions

View File

@ -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"),

View File

@ -135,7 +135,7 @@
{{#if this.existingUserCanRedeem}}
<DButton @class="btn-primary" @action={{action "submit"}} @type="submit" @disabled={{this.submitDisabled}} @label="invites.accept_invite" />
{{else}}
<div class="alert alert-error">{{i18n "invites.existing_user_cannot_redeem"}}</div>
<div class="alert alert-error">{{this.existingUserCanRedeemError}}</div>
{{/if}}
{{/if}}
{{/if}}

View File

@ -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

View File

@ -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

View File

@ -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}"

View File

@ -250,6 +250,8 @@ en:
<p>Otherwise please <a href="%{base_url}/password-reset">Reset Password</a>.</p>
not_found_template_link: |
<p>This invitation to <a href="%{base_url}">%{site_name}</a> can no longer be redeemed. Please ask the person who invited you to send you a new invitation.</p>
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 <b>%{email}</b>, they already have an account!"
invite_exists: "You already invited <b>%{email}</b>."
invalid_email: "%{email} isn't a valid email address."

View File

@ -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