mirror of
https://github.com/discourse/discourse.git
synced 2024-12-12 06:23:41 +08:00
FIX: Backport invite fixes from main (#19218)
Backports the following: *40e8912395
*bbcb69461f
Which were showing an error when users were trying to claim invites multiple times and a subsequent follow-up fix.
This commit is contained in:
parent
15823d4a50
commit
27d93b4658
|
@ -31,6 +31,9 @@ export default Controller.extend(
|
||||||
accountEmail: alias("email"),
|
accountEmail: alias("email"),
|
||||||
existingUserId: readOnly("model.existing_user_id"),
|
existingUserId: readOnly("model.existing_user_id"),
|
||||||
existingUserCanRedeem: readOnly("model.existing_user_can_redeem"),
|
existingUserCanRedeem: readOnly("model.existing_user_can_redeem"),
|
||||||
|
existingUserCanRedeemError: readOnly(
|
||||||
|
"model.existing_user_can_redeem_error"
|
||||||
|
),
|
||||||
existingUserRedeeming: bool("existingUserId"),
|
existingUserRedeeming: bool("existingUserId"),
|
||||||
hiddenEmail: alias("model.hidden_email"),
|
hiddenEmail: alias("model.hidden_email"),
|
||||||
emailVerifiedByLink: alias("model.email_verified_by_link"),
|
emailVerifiedByLink: alias("model.email_verified_by_link"),
|
||||||
|
|
|
@ -136,7 +136,7 @@
|
||||||
{{#if this.existingUserCanRedeem}}
|
{{#if this.existingUserCanRedeem}}
|
||||||
<DButton @class="btn-primary" @action={{action "submit"}} @type="submit" @disabled={{this.submitDisabled}} @label="invites.accept_invite" />
|
<DButton @class="btn-primary" @action={{action "submit"}} @type="submit" @disabled={{this.submitDisabled}} @label="invites.accept_invite" />
|
||||||
{{else}}
|
{{else}}
|
||||||
<div class="alert alert-error">{{i18n "invites.existing_user_cannot_redeem"}}</div>
|
<div class="alert alert-error">{{this.existingUserCanRedeemError}}</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
|
@ -391,6 +391,7 @@ class InvitesController < ApplicationController
|
||||||
if current_user
|
if current_user
|
||||||
info[:existing_user_id] = current_user.id
|
info[:existing_user_id] = current_user.id
|
||||||
info[:existing_user_can_redeem] = invite.can_be_redeemed_by?(current_user)
|
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[:email] = current_user.email
|
||||||
info[:username] = current_user.username
|
info[:username] = current_user.username
|
||||||
end
|
end
|
||||||
|
@ -467,4 +468,13 @@ class InvitesController < ApplicationController
|
||||||
end
|
end
|
||||||
end
|
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
|
end
|
||||||
|
|
|
@ -82,6 +82,10 @@ class Invite < ActiveRecord::Base
|
||||||
!redeemed? && !expired? && !deleted_at? && !destroyed? && link_valid?
|
!redeemed? && !expired? && !deleted_at? && !destroyed? && link_valid?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def redeemed_by_user?(redeeming_user)
|
||||||
|
self.invited_users.exists?(user: redeeming_user)
|
||||||
|
end
|
||||||
|
|
||||||
def redeemed?
|
def redeemed?
|
||||||
if is_invite_link?
|
if is_invite_link?
|
||||||
redemption_count >= max_redemptions_allowed
|
redemption_count >= max_redemptions_allowed
|
||||||
|
@ -101,6 +105,7 @@ class Invite < ActiveRecord::Base
|
||||||
|
|
||||||
def can_be_redeemed_by?(user)
|
def can_be_redeemed_by?(user)
|
||||||
return false if !self.redeemable?
|
return false if !self.redeemable?
|
||||||
|
return false if redeemed_by_user?(user)
|
||||||
return true if self.email.blank? && self.domain.blank?
|
return true if self.email.blank? && self.domain.blank?
|
||||||
return true if self.email.present? && email_matches?(user.email)
|
return true if self.email.present? && email_matches?(user.email)
|
||||||
self.domain.present? && domain_matches?(user.email)
|
self.domain.present? && domain_matches?(user.email)
|
||||||
|
|
|
@ -1989,7 +1989,6 @@ en:
|
||||||
password_label: "Password"
|
password_label: "Password"
|
||||||
optional_description: "(optional)"
|
optional_description: "(optional)"
|
||||||
existing_user_can_redeem: "Redeem your invitation to a topic or group."
|
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:
|
password_reset:
|
||||||
continue: "Continue to %{site_name}"
|
continue: "Continue to %{site_name}"
|
||||||
|
|
|
@ -248,6 +248,8 @@ en:
|
||||||
<p>Otherwise please <a href="%{base_url}/password-reset">Reset Password</a>.</p>
|
<p>Otherwise please <a href="%{base_url}/password-reset">Reset Password</a>.</p>
|
||||||
not_found_template_link: |
|
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>
|
<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 <a href='%{base_path}/u/%{username}/summary'>already have an account!</a>"
|
user_exists: "There's no need to invite <b>%{email}</b>, they <a href='%{base_path}/u/%{username}/summary'>already have an account!</a>"
|
||||||
invite_exists: "You already invited <b>%{email}</b>."
|
invite_exists: "You already invited <b>%{email}</b>."
|
||||||
invalid_email: "%{email} isn't a valid email address."
|
invalid_email: "%{email} isn't a valid email address."
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
require 'rails_helper'
|
require 'rails_helper'
|
||||||
|
|
||||||
describe Invite do
|
describe Invite do
|
||||||
fab!(:user) { Fabricate(:user) }
|
fab!(:user) { Fabricate(:user, email: "existinguser@invitetest.com") }
|
||||||
let(:xss_email) { "<b onmouseover=alert('wufff!')>email</b><script>alert('test');</script>@test.com" }
|
let(:xss_email) { "<b onmouseover=alert('wufff!')>email</b><script>alert('test');</script>@test.com" }
|
||||||
let(:escaped_email) { "<b onmouseover=alert('wufff!')>email</b><script>alert('test');</script>@test.com" }
|
let(:escaped_email) { "<b onmouseover=alert('wufff!')>email</b><script>alert('test');</script>@test.com" }
|
||||||
|
|
||||||
|
@ -481,4 +481,91 @@ describe Invite do
|
||||||
expect(invite.warnings(admin.guardian)).to contain_exactly(I18n.t("invite.requires_groups", groups: group.name))
|
expect(invite.warnings(admin.guardian)).to contain_exactly(I18n.t("invite.requires_groups", groups: group.name))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "#can_be_redeemed_by?" do
|
||||||
|
context "for invite links" do
|
||||||
|
fab!(:invite) { Fabricate(:invite, email: nil, domain: nil, max_redemptions_allowed: 1) }
|
||||||
|
|
||||||
|
it "returns false if invite is already redeemed" do
|
||||||
|
invite.update!(redemption_count: 1)
|
||||||
|
expect(invite.can_be_redeemed_by?(user)).to eq(false)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns false if the invite is expired" do
|
||||||
|
invite.update!(expires_at: 10.days.ago)
|
||||||
|
expect(invite.can_be_redeemed_by?(user)).to eq(false)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns false if invite is deleted" do
|
||||||
|
invite.trash!
|
||||||
|
expect(invite.can_be_redeemed_by?(user)).to eq(false)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns false if invite is invalidated" do
|
||||||
|
invite.update!(invalidated_at: 1.day.ago)
|
||||||
|
expect(invite.can_be_redeemed_by?(user)).to eq(false)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns false if the user already redeemed it" do
|
||||||
|
InvitedUser.create(user: user, invite: invite)
|
||||||
|
expect(invite.can_be_redeemed_by?(user)).to eq(false)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns false if domain does not match user email" do
|
||||||
|
invite.update!(domain: "zzzzz.com")
|
||||||
|
expect(invite.can_be_redeemed_by?(user)).to eq(false)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns true if domain does match user email" do
|
||||||
|
invite.update!(domain: "invitetest.com")
|
||||||
|
expect(invite.can_be_redeemed_by?(user)).to eq(true)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns true by default if all other conditions are met and domain and invite are blank" do
|
||||||
|
expect(invite.can_be_redeemed_by?(user)).to eq(true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "for email invites" do
|
||||||
|
fab!(:invite) do
|
||||||
|
invite = Fabricate(:invite, email: "otherexisting@invitetest.com", domain: nil)
|
||||||
|
user.update!(email: "otherexisting@invitetest.com")
|
||||||
|
invite
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns false if invite is already redeemed" do
|
||||||
|
InvitedUser.create(user: Fabricate(:user), invite: invite)
|
||||||
|
expect(invite.can_be_redeemed_by?(user)).to eq(false)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns false if the invite is expired" do
|
||||||
|
invite.update!(expires_at: 10.days.ago)
|
||||||
|
expect(invite.can_be_redeemed_by?(user)).to eq(false)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns false if invite is deleted" do
|
||||||
|
invite.trash!
|
||||||
|
expect(invite.can_be_redeemed_by?(user)).to eq(false)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns false if invite is invalidated" do
|
||||||
|
invite.update!(invalidated_at: 1.day.ago)
|
||||||
|
expect(invite.can_be_redeemed_by?(user)).to eq(false)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns false if the user already redeemed it" do
|
||||||
|
InvitedUser.create(user: user, invite: invite)
|
||||||
|
expect(invite.can_be_redeemed_by?(user)).to eq(false)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns false if email does not match user email" do
|
||||||
|
invite.update!(email: "blahblah@test.com")
|
||||||
|
expect(invite.can_be_redeemed_by?(user)).to eq(false)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns true if email does match user email" do
|
||||||
|
expect(invite.can_be_redeemed_by?(user)).to eq(true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -117,6 +117,7 @@ describe InvitesController do
|
||||||
json = JSON.parse(element.current_scope.attribute('data-preloaded').value)
|
json = JSON.parse(element.current_scope.attribute('data-preloaded').value)
|
||||||
invite_info = JSON.parse(json['invite_info'])
|
invite_info = JSON.parse(json['invite_info'])
|
||||||
expect(invite_info['existing_user_can_redeem']).to eq(false)
|
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
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -130,6 +131,37 @@ describe InvitesController do
|
||||||
json = JSON.parse(element.current_scope.attribute('data-preloaded').value)
|
json = JSON.parse(element.current_scope.attribute('data-preloaded').value)
|
||||||
invite_info = JSON.parse(json['invite_info'])
|
invite_info = JSON.parse(json['invite_info'])
|
||||||
expect(invite_info['existing_user_can_redeem']).to eq(false)
|
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
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
it "allows the user to accept the invite when its an invite link that they have not redeemed" do
|
||||||
|
invite.update!(email: nil, max_redemptions_allowed: 10)
|
||||||
|
|
||||||
|
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(true)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in New Issue
Block a user