Skip to content

Commit 0d3da6f

Browse files
authored
fix: case sensitivity issue with email invites (#1486)
Fixes: HDX-3055 Fixes: #1482
1 parent dc84601 commit 0d3da6f

File tree

4 files changed

+84
-4
lines changed

4 files changed

+84
-4
lines changed

.changeset/tasty-clouds-listen.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@hyperdx/api": patch
3+
---
4+
5+
fix: case sensitivity issue with email invites

packages/api/src/controllers/user.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,16 @@ export function findUserById(id: string) {
1212
}
1313

1414
export function findUserByEmail(email: string) {
15-
return User.findOne({ email });
15+
// Case-insensitive email search - lowercase the email since User model stores emails in lowercase
16+
return User.findOne({ email: email.toLowerCase() });
1617
}
1718

1819
export async function findUserByEmailInTeam(
1920
email: string,
2021
team: string | ObjectId,
2122
) {
22-
return User.findOne({ email, team });
23+
// Case-insensitive email search - lowercase the email since User model stores emails in lowercase
24+
return User.findOne({ email: email.toLowerCase(), team });
2325
}
2426

2527
export function findUsersByTeam(team: string | ObjectId) {

packages/api/src/routers/api/__tests__/team.test.ts

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,75 @@ Array [
140140
expect(resp.body.url).toContain(`/join-team?token=${teamInvite.token}`);
141141
});
142142

143+
it('POST /team/invitation with different case reuses existing invitation', async () => {
144+
const { agent } = await getLoggedInAgent(server);
145+
146+
// Create invitation with lowercase email
147+
const resp1 = await agent
148+
.post('/team/invitation')
149+
.send({
150+
151+
name: 'Case Test',
152+
})
153+
.expect(200);
154+
155+
const firstInvite = await TeamInvite.findOne({
156+
157+
});
158+
expect(firstInvite).not.toBeNull();
159+
const firstToken = firstInvite!.token;
160+
161+
// Try to create invitation with uppercase email
162+
const resp2 = await agent
163+
.post('/team/invitation')
164+
.send({
165+
166+
name: 'Case Test 2',
167+
})
168+
.expect(200);
169+
170+
// Should reuse the same invitation (same token)
171+
const secondInvite = await TeamInvite.findOne({
172+
173+
});
174+
expect(secondInvite).not.toBeNull();
175+
expect(secondInvite!.token).toBe(firstToken);
176+
177+
// Should only have one invitation in the database
178+
const allInvites = await TeamInvite.find({
179+
180+
});
181+
expect(allInvites).toHaveLength(1);
182+
});
183+
184+
it('POST /team/invitation rejects existing user regardless of email case', async () => {
185+
const { agent, team } = await getLoggedInAgent(server);
186+
187+
// Create a user with lowercase email
188+
await User.create({
189+
190+
team: team.id,
191+
});
192+
193+
// Try to invite with different casing - should be rejected
194+
await agent
195+
.post('/team/invitation')
196+
.send({
197+
198+
name: 'Existing User',
199+
})
200+
.expect(400);
201+
202+
// Try to invite with exact same casing - should also be rejected
203+
await agent
204+
.post('/team/invitation')
205+
.send({
206+
207+
name: 'Existing User',
208+
})
209+
.expect(400);
210+
});
211+
143212
it('GET /team/invitations', async () => {
144213
const { agent } = await getLoggedInAgent(server);
145214
await Promise.all([

packages/api/src/routers/api/team.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -151,16 +151,20 @@ router.post(
151151
});
152152
}
153153

154+
// Normalize email to lowercase for consistency
155+
const normalizedEmail = toEmail.toLowerCase();
156+
157+
// Check for existing invitation with normalized email
154158
let teamInvite = await TeamInvite.findOne({
155159
teamId,
156-
email: toEmail, // TODO: case insensitive ?
160+
email: normalizedEmail,
157161
});
158162

159163
if (!teamInvite) {
160164
teamInvite = await new TeamInvite({
161165
teamId,
162166
name,
163-
email: toEmail, // TODO: case insensitive ?
167+
email: normalizedEmail,
164168
token: crypto.randomBytes(32).toString('hex'),
165169
}).save();
166170
}

0 commit comments

Comments
 (0)