0
Fork 0
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:
Charles Zhao 2024-04-17 17:55:54 +08:00 committed by GitHub
parent 2de2939e30
commit 52df3ebbbb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 15 additions and 8 deletions

View file

@ -0,0 +1,5 @@
---
"@logto/core": patch
---
Bug fix: organization invitation APIs should handle invitee emails case insensitively

View file

@ -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);
} }

View file

@ -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 }) => {

View file

@ -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 () => {