Skip to content

Commit dcef3c0

Browse files
authored
fix: lowercase the email when requesting an email change (#1940)
* fix: lowercase the email when requesting an email change * fix: typo in .toLowerCase * fix: compare with lowercase email
1 parent a12fe94 commit dcef3c0

3 files changed

Lines changed: 40 additions & 5 deletions

File tree

src/services/auth/plugins/passport/strategies/emailChange.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,16 @@ export default (
2121
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
2222
secretOrKey: EMAIL_CHANGE_JWT_SECRET,
2323
},
24-
async ({ uuid, oldEmail, newEmail }, done: StrictVerifiedCallback) => {
24+
async (
25+
{
26+
uuid,
27+
oldEmail,
28+
newEmail: newEmailRaw,
29+
}: { uuid: string; oldEmail: string; newEmail: string },
30+
done: StrictVerifiedCallback,
31+
) => {
2532
try {
33+
const newEmail = newEmailRaw.toLowerCase();
2634
// We shouldn't fetch the member by email, so we keep track of the actual member.
2735
const member = await memberRepository.get(db, uuid);
2836
// We check the email, so we invalidate the token if the email has changed in the meantime.

src/services/member/member.controller.email.storage.test.ts

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ describe('Member Storage Controller', () => {
142142
expect(response.statusCode).toBe(StatusCodes.NO_CONTENT);
143143
await waitForExpect(() => {
144144
expect(mockSendEmail).toHaveBeenCalledTimes(1);
145-
expect(mockSendEmail.mock.calls[0][1]).toBe(email);
145+
expect(mockSendEmail.mock.calls[0][1]).toBe(email.toLowerCase());
146146
expect(mockSendEmail.mock.calls[0][2]).toContain('email/change?t=');
147147
});
148148

@@ -263,6 +263,32 @@ describe('Member Storage Controller', () => {
263263
// mock send email only sent once, before
264264
expect(mockSendEmail).toHaveBeenCalledTimes(1);
265265
});
266+
// regression test for issue #1939
267+
it('Change email and store as lowercase', async () => {
268+
const { actor } = await seedFromJson();
269+
assertIsDefined(actor);
270+
assertIsMember(actor);
271+
mockAuthenticate(actor);
272+
273+
// specifically use an uppercase email
274+
const newEmail = faker.internet.email().toUpperCase();
275+
const token = jwtSign(
276+
{ uuid: actor.id, oldEmail: actor.email, newEmail },
277+
EMAIL_CHANGE_JWT_SECRET,
278+
);
279+
280+
const response = await app.inject({
281+
method: HttpMethod.Patch,
282+
url: '/members/current/email/change',
283+
headers: { Authorization: `Bearer ${token}` },
284+
});
285+
expect(response.statusCode).toBe(StatusCodes.NO_CONTENT);
286+
// Email changed
287+
const rawMember = await db.query.accountsTable.findFirst({
288+
where: eq(accountsTable.id, actor.id),
289+
});
290+
expect(rawMember?.email).toEqual(newEmail.toLowerCase());
291+
});
266292
});
267293
describe('GET /members/current/storage/files', () => {
268294
it('returns ok', async () => {

src/services/member/member.controller.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -141,11 +141,12 @@ const controller: FastifyPluginAsyncTypebox = async (fastify) => {
141141
preHandler: [isAuthenticated, matchOne(memberAccountRole)],
142142
},
143143
async ({ user, body: { email } }, reply) => {
144+
const newEmail = email.toLowerCase();
144145
const account = asDefined(user?.account);
145146
assertIsMember(account);
146147

147148
// check if there is a member that already has the new email
148-
if (await memberService.getByEmail(db, email)) {
149+
if (await memberService.getByEmail(db, newEmail)) {
149150
// Email adress is already taken, throw an error
150151
throw new EmailAlreadyTaken();
151152
}
@@ -154,8 +155,8 @@ const controller: FastifyPluginAsyncTypebox = async (fastify) => {
154155
const member = await memberService.get(db, account.id);
155156
assertIsDefined(member);
156157
const memberInfo = member.toMemberInfo();
157-
const token = memberService.createEmailChangeRequest(memberInfo, email);
158-
memberService.sendEmailChangeRequest(email, token, memberInfo.lang);
158+
const token = memberService.createEmailChangeRequest(memberInfo, newEmail);
159+
memberService.sendEmailChangeRequest(newEmail, token, memberInfo.lang);
159160

160161
reply.status(StatusCodes.NO_CONTENT);
161162
},

0 commit comments

Comments
 (0)