mirror of
https://github.com/logto-io/logto.git
synced 2024-12-16 20:26:19 -05:00
fix(core,console): invitee emails should be case insensitive (#5730)
* fix(core,console): invitee email checks should be case insensitive * test: add integration test * chore: add changeset
This commit is contained in:
parent
2de2939e30
commit
52df3ebbbb
4 changed files with 15 additions and 8 deletions
5
.changeset/afraid-stingrays-perform.md
Normal file
5
.changeset/afraid-stingrays-perform.md
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
"@logto/core": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Bug fix: organization invitation APIs should handle invitee emails case insensitively
|
|
@ -44,15 +44,16 @@ const useEmailInputUtils = () => {
|
||||||
const validEmails = new Set<string>();
|
const validEmails = new Set<string>();
|
||||||
|
|
||||||
const existingMemberEmails = new Set<string>(
|
const existingMemberEmails = new Set<string>(
|
||||||
existingMembers.map(({ primaryEmail }) => primaryEmail ?? '').filter(Boolean)
|
existingMembers.map(({ primaryEmail }) => primaryEmail?.toLowerCase() ?? '').filter(Boolean)
|
||||||
);
|
);
|
||||||
const existingInvitationEmails = new Set<string>(
|
const existingInvitationEmails = new Set<string>(
|
||||||
existingInvitations
|
existingInvitations
|
||||||
.filter(({ status }) => status === OrganizationInvitationStatus.Pending)
|
.filter(({ status }) => status === OrganizationInvitationStatus.Pending)
|
||||||
.map(({ invitee }) => invitee)
|
.map(({ invitee }) => invitee.toLowerCase())
|
||||||
);
|
);
|
||||||
|
|
||||||
for (const email of emails) {
|
for (const userInputEmail of emails) {
|
||||||
|
const email = userInputEmail.toLowerCase();
|
||||||
if (!emailRegEx.test(email)) {
|
if (!emailRegEx.test(email)) {
|
||||||
invalidEmails.add(email);
|
invalidEmails.add(email);
|
||||||
}
|
}
|
||||||
|
|
|
@ -165,7 +165,7 @@ class OrganizationInvitationsQueries extends SchemaQueries<
|
||||||
return sql`and ${fields.organizationId} = ${id}`;
|
return sql`and ${fields.organizationId} = ${id}`;
|
||||||
})}
|
})}
|
||||||
${conditionalSql(invitee, (email) => {
|
${conditionalSql(invitee, (email) => {
|
||||||
return sql`and ${fields.invitee} = ${email}`;
|
return sql`and lower(${fields.invitee}) = lower(${email})`;
|
||||||
})}
|
})}
|
||||||
`);
|
`);
|
||||||
}
|
}
|
||||||
|
@ -220,7 +220,7 @@ class OrganizationInvitationsQueries extends SchemaQueries<
|
||||||
return sql`and ${fields.inviterId} = ${id}`;
|
return sql`and ${fields.inviterId} = ${id}`;
|
||||||
})}
|
})}
|
||||||
${conditionalSql(invitee, (email) => {
|
${conditionalSql(invitee, (email) => {
|
||||||
return sql`and ${fields.invitee} = ${email}`;
|
return sql`and lower(${fields.invitee}) = lower(${email})`;
|
||||||
})}
|
})}
|
||||||
group by ${fields.id}
|
group by ${fields.id}
|
||||||
${conditionalSql(this.orderBy, ({ field, order }) => {
|
${conditionalSql(this.orderBy, ({ field, order }) => {
|
||||||
|
|
|
@ -61,18 +61,19 @@ describe('organization invitation creation', () => {
|
||||||
await Promise.all([deleteUser(inviter.id), deleteUser(inviter2.id)]);
|
await Promise.all([deleteUser(inviter.id), deleteUser(inviter2.id)]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should be able to get invitations by invitee', async () => {
|
it('should be able to get invitations by invitee email (case insensitive)', async () => {
|
||||||
const organization = await organizationApi.create({ name: 'test' });
|
const organization = await organizationApi.create({ name: 'test' });
|
||||||
const invitee = `${randomId()}@example.com`;
|
const invitee = `${randomId()}@example.com`;
|
||||||
await invitationApi.create({
|
await invitationApi.create({
|
||||||
organizationId: organization.id,
|
organizationId: organization.id,
|
||||||
invitee,
|
// Deliberately use uppercase email when creating the invitation
|
||||||
|
invitee: invitee.toUpperCase(),
|
||||||
expiresAt: Date.now() + 1_000_000,
|
expiresAt: Date.now() + 1_000_000,
|
||||||
});
|
});
|
||||||
|
|
||||||
const invitations = await invitationApi.getList(new URLSearchParams({ invitee }));
|
const invitations = await invitationApi.getList(new URLSearchParams({ invitee }));
|
||||||
expect(invitations.length).toBe(1);
|
expect(invitations.length).toBe(1);
|
||||||
expect(invitations[0]?.invitee).toBe(invitee);
|
expect(invitations[0]?.invitee.toLocaleLowerCase()).toBe(invitee.toLowerCase());
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should have no pagination', async () => {
|
it('should have no pagination', async () => {
|
||||||
|
|
Loading…
Reference in a new issue