fix: Require mfa code to change email (#10354)

This commit is contained in:
Tomi Turtiainen
2024-08-15 10:36:04 +03:00
committed by GitHub
parent 483e324d4c
commit 39c8e50ad0
8 changed files with 181 additions and 41 deletions

View File

@@ -53,9 +53,14 @@ describe('MeController', () => {
password: 'password',
authIdentities: [],
role: 'global:owner',
mfaEnabled: false,
});
const reqBody = { email: 'valid@email.com', firstName: 'John', lastName: 'Potato' };
const req = mock<MeRequest.UserUpdate>({ user, body: reqBody, browserId });
const req = mock<MeRequest.UserUpdate>({ user, browserId });
req.body = {
email: 'valid@email.com',
firstName: 'John',
lastName: 'Potato',
};
const res = mock<Response>();
userRepository.findOneByOrFail.mockResolvedValue(user);
userRepository.findOneOrFail.mockResolvedValue(user);
@@ -67,7 +72,7 @@ describe('MeController', () => {
expect(externalHooks.run).toHaveBeenCalledWith('user.profile.beforeUpdate', [
user.id,
user.email,
reqBody,
req.body,
]);
expect(userService.update).toHaveBeenCalled();
@@ -98,25 +103,25 @@ describe('MeController', () => {
password: 'password',
authIdentities: [],
role: 'global:member',
mfaEnabled: false,
});
const reqBody = { email: 'valid@email.com', firstName: 'John', lastName: 'Potato' };
const req = mock<MeRequest.UserUpdate>({ user, browserId });
req.body = reqBody;
req.body = { email: 'valid@email.com', firstName: 'John', lastName: 'Potato' };
const res = mock<Response>();
userRepository.findOneOrFail.mockResolvedValue(user);
jest.spyOn(jwt, 'sign').mockImplementation(() => 'signed-token');
// Add invalid data to the request payload
Object.assign(reqBody, { id: '0', role: 'global:owner' });
Object.assign(req.body, { id: '0', role: 'global:owner' });
await controller.updateCurrentUser(req, res);
expect(userService.update).toHaveBeenCalled();
const updatePayload = userService.update.mock.calls[0][1];
expect(updatePayload.email).toBe(reqBody.email);
expect(updatePayload.firstName).toBe(reqBody.firstName);
expect(updatePayload.lastName).toBe(reqBody.lastName);
expect(updatePayload.email).toBe(req.body.email);
expect(updatePayload.firstName).toBe(req.body.firstName);
expect(updatePayload.lastName).toBe(req.body.lastName);
expect(updatePayload.id).toBeUndefined();
expect(updatePayload.role).toBeUndefined();
});
@@ -127,10 +132,11 @@ describe('MeController', () => {
password: 'password',
authIdentities: [],
role: 'global:owner',
mfaEnabled: false,
});
const reqBody = { email: 'valid@email.com', firstName: 'John', lastName: 'Potato' };
const req = mock<MeRequest.UserUpdate>({ user, body: reqBody });
// userService.findOneOrFail.mockResolvedValue(user);
req.body = reqBody; // We don't want the body to be a mock object
externalHooks.run.mockImplementationOnce(async (hookName) => {
if (hookName === 'user.profile.beforeUpdate') {
@@ -142,6 +148,76 @@ describe('MeController', () => {
new BadRequestError('Invalid email address'),
);
});
describe('when mfa is enabled', () => {
it('should throw BadRequestError if mfa code is missing', async () => {
const user = mock<User>({
id: '123',
email: 'valid@email.com',
password: 'password',
authIdentities: [],
role: 'global:owner',
mfaEnabled: true,
});
const req = mock<MeRequest.UserUpdate>({ user, browserId });
req.body = { email: 'new@email.com', firstName: 'John', lastName: 'Potato' };
await expect(controller.updateCurrentUser(req, mock())).rejects.toThrowError(
new BadRequestError('Two-factor code is required to change email'),
);
});
it('should throw InvalidMfaCodeError if mfa code is invalid', async () => {
const user = mock<User>({
id: '123',
email: 'valid@email.com',
password: 'password',
authIdentities: [],
role: 'global:owner',
mfaEnabled: true,
});
const req = mock<MeRequest.UserUpdate>({ user, browserId });
req.body = {
email: 'new@email.com',
firstName: 'John',
lastName: 'Potato',
mfaCode: 'invalid',
};
mockMfaService.validateMfa.mockResolvedValue(false);
await expect(controller.updateCurrentUser(req, mock())).rejects.toThrow(
InvalidMfaCodeError,
);
});
it("should update the user's email if mfa code is valid", async () => {
const user = mock<User>({
id: '123',
email: 'valid@email.com',
password: 'password',
authIdentities: [],
role: 'global:owner',
mfaEnabled: true,
});
const req = mock<MeRequest.UserUpdate>({ user, browserId });
req.body = {
email: 'new@email.com',
firstName: 'John',
lastName: 'Potato',
mfaCode: '123456',
};
const res = mock<Response>();
userRepository.findOneByOrFail.mockResolvedValue(user);
userRepository.findOneOrFail.mockResolvedValue(user);
jest.spyOn(jwt, 'sign').mockImplementation(() => 'signed-token');
userService.toPublic.mockResolvedValue({} as unknown as PublicUser);
mockMfaService.validateMfa.mockResolvedValue(true);
const result = await controller.updateCurrentUser(req, res);
expect(result).toEqual({});
});
});
});
describe('updatePassword', () => {