-
-
Notifications
You must be signed in to change notification settings - Fork 1.4k
fix(webapp): match org invite emails case-insensitively #4434
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| --- | ||
| area: webapp | ||
| type: fix | ||
| --- | ||
|
|
||
| Org member invites now match emails case-insensitively, so an invite whose email casing differs from the invitee's account email can be accepted. Re-inviting an already-invited email now resends the invite instead of failing. | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -134,7 +134,12 @@ export async function inviteMembers({ | |
| const existingMembers = await prisma.orgMember.findMany({ | ||
| where: { | ||
| organizationId: org.id, | ||
| user: { email: { in: [...uniqueEmails] } }, | ||
| user: { | ||
| email: { | ||
| in: [...uniqueEmails], | ||
| mode: "insensitive", | ||
| }, | ||
| }, | ||
|
Comment on lines
+137
to
+142
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 Someone who is already an organization member can still be invited again when their email casing differs Existing members found by the new casing-insensitive lookup are then compared with an exact, casing-sensitive string check ( Impact: Existing team members receive a redundant "you've been invited" email and appear as a pending invite on the team page. Set membership comparison defeats the insensitive queryThe query at lines 134-145 now matches member emails with For a member stored as Normalising both sides (e.g. lowercasing the set entries and the lookup key) makes the widened query actually effective. Prompt for agentsWas this helpful? React with 👍 or 👎 to provide feedback. |
||
| }, | ||
| select: { user: { select: { email: true } } }, | ||
| }); | ||
|
|
@@ -203,7 +208,7 @@ export async function getInviteFromToken({ token }: { token: string }) { | |
| export async function getUsersInvites({ email }: { email: string }) { | ||
| return await prisma.orgMemberInvite.findMany({ | ||
| where: { | ||
| email, | ||
| email: { equals: email, mode: "insensitive" }, | ||
| organization: { | ||
| deletedAt: null, | ||
| }, | ||
|
|
@@ -562,7 +567,7 @@ export async function acceptInvite({ | |
| await prisma.orgMemberInvite.delete({ | ||
| where: { | ||
| id: inviteId, | ||
| email: user.email, | ||
| email: { equals: user.email, mode: "insensitive" }, | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔴 Invites shown to a user whose email casing differs still cannot be accepted The invite is still looked up by an exact, casing-sensitive email match ( Impact: Users see the pending invitation on the invites page but get an "invite not found" error every time they try to accept it, so they can never join the organization. Why the case-insensitive delete is never reached
When the user clicks Accept, The insensitive Note this is a regression in visible behaviour: before this change the mismatched invite simply wasn't listed, now it is listed but always errors. Prompt for agentsWas this helpful? React with 👍 or 👎 to provide feedback. |
||
| }, | ||
| }); | ||
| } catch (error) { | ||
|
|
@@ -573,6 +578,15 @@ export async function acceptInvite({ | |
| } | ||
| } | ||
|
|
||
| // Consume any case-variant duplicate invites for this org (rows created | ||
| // before invite emails were lowercased) | ||
| await prisma.orgMemberInvite.deleteMany({ | ||
| where: { | ||
| organizationId: invite.organizationId, | ||
| email: { equals: user.email, mode: "insensitive" }, | ||
| }, | ||
| }); | ||
|
|
||
| const remainingInvites = await getUsersInvites({ email: user.email }); | ||
|
|
||
| if (invite.rbacRoleId) { | ||
|
|
@@ -605,7 +619,7 @@ export async function declineInvite({ | |
| const declinedInvite = await tx.orgMemberInvite.delete({ | ||
| where: { | ||
| id: inviteId, | ||
| email: user.email, | ||
| email: { equals: user.email, mode: "insensitive" }, | ||
| }, | ||
| include: { | ||
| organization: true, | ||
|
|
@@ -615,7 +629,7 @@ export async function declineInvite({ | |
| //2. check for other invites | ||
| const remainingInvites = await tx.orgMemberInvite.findMany({ | ||
| where: { | ||
| email: user.email, | ||
| email: { equals: user.email, mode: "insensitive" }, | ||
| }, | ||
| }); | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -123,7 +123,7 @@ const schema = z.object({ | |
| } | ||
|
|
||
| return [""]; | ||
| }, z.string().email().array().nonempty("At least one email is required")), | ||
| }, z.string().trim().toLowerCase().email().array().nonempty("At least one email is required")), | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 Invitations created through the public API are not lowercased, so duplicate invites for the same person are still possible Only the dashboard form normalises the invitee address ( Impact: Inviting the same person twice with different capitalisation via the API creates duplicate invitations instead of being recognised as already invited. Second invite entry point missed by the normalisation
Because the Applying the same Prompt for agentsWas this helpful? React with 👍 or 👎 to provide feedback. |
||
| rbacRoleId: z.string().optional(), | ||
| }); | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🔍 Release note claims a resend behaviour the code does not implement
The note states "Re-inviting an already-invited email now resends the invite instead of failing."
inviteMembers(apps/webapp/app/models/member.server.ts:159-191) still catches the P2002 on a duplicate org+email invite, pushes the address intoalreadyInvited, and does not touch the existing row — noresendInvitecall, noupdatedAtbump, no email sent for it. Per AGENTS.md this text ships verbatim in user-visible release notes, so it should be corrected to describe what actually happens (duplicates are reported as already invited).Was this helpful? React with 👍 or 👎 to provide feedback.