diff --git a/.changeset/open-toes-vanish.md b/.changeset/open-toes-vanish.md new file mode 100644 index 0000000000000..fc98067aa24f5 --- /dev/null +++ b/.changeset/open-toes-vanish.md @@ -0,0 +1,6 @@ +--- +'@rocket.chat/meteor': patch +'@rocket.chat/web-ui-registration': patch +--- + +Fixes email validation in the registration form to prevent users from registering with an already existing email address. diff --git a/apps/meteor/app/api/server/v1/users.ts b/apps/meteor/app/api/server/v1/users.ts index d65f5bcc42caa..c84c5a361d35e 100644 --- a/apps/meteor/app/api/server/v1/users.ts +++ b/apps/meteor/app/api/server/v1/users.ts @@ -44,6 +44,7 @@ import { executeSetUserActiveStatus } from '../../../../server/methods/setUserAc import { getUserForCheck, emailCheck } from '../../../2fa/server/code'; import { resetTOTP } from '../../../2fa/server/functions/resetTOTP'; import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission'; +import { checkEmailAvailability } from '../../../lib/server/functions/checkEmailAvailability'; import { checkUsernameAvailability, checkUsernameAvailabilityWithValidation, @@ -663,7 +664,9 @@ API.v1.addRoute( if (!(await checkUsernameAvailability(this.bodyParams.username))) { return API.v1.failure('Username is already in use'); } - + if (!(await checkEmailAvailability(this.bodyParams.email))) { + return API.v1.failure('Email already exists'); + } if (this.bodyParams.customFields) { try { await validateCustomFields(this.bodyParams.customFields); diff --git a/apps/meteor/tests/e2e/register.spec.ts b/apps/meteor/tests/e2e/register.spec.ts index 0e371aa25d972..bf0e8029654c5 100644 --- a/apps/meteor/tests/e2e/register.spec.ts +++ b/apps/meteor/tests/e2e/register.spec.ts @@ -1,6 +1,7 @@ import { faker } from '@faker-js/faker'; import { Utils, Registration } from './page-objects'; +import { request } from '../data/api-data'; import { test, expect } from './utils/test'; test.describe.parallel('register', () => { @@ -12,7 +13,8 @@ test.describe.parallel('register', () => { poRegistration = new Registration(page); poUtils = new Utils(page); }); - test('Successfully Registration flow', async ({ page }) => { + + test('should complete the registration flow', async ({ page }) => { await test.step('expect trigger a validation error if no data is provided on register', async () => { await page.goto('/home'); await poRegistration.goToRegister.click(); @@ -43,24 +45,27 @@ test.describe.parallel('register', () => { }); }); - test.describe('Registration without Account confirmation password set', async () => { + test.describe('should complete registration without account confirmation password set', async () => { test.beforeEach(async ({ api }) => { const result = await api.post('/settings/Accounts_RequirePasswordConfirmation', { value: false }); await expect(result.ok()).toBeTruthy(); }); + test.beforeEach(async ({ page }) => { await page.goto('/home'); await poRegistration.goToRegister.click(); }); + test.afterEach(async ({ api }) => { const result = await api.post('/settings/Accounts_RequirePasswordConfirmation', { value: true, }); + await expect(result.ok()).toBeTruthy(); }); - test('expect to register a user without password confirmation', async () => { + test('should register a user without password confirmation', async () => { await test.step('expect to not have password confirmation field', async () => { await expect(poRegistration.inputPasswordConfirm).toBeHidden(); }); @@ -77,12 +82,13 @@ test.describe.parallel('register', () => { }); }); - test.describe('Registration with manually confirmation enabled', async () => { + test.describe('should complete registration with manually confirmation enabled', async () => { test.beforeEach(async ({ api }) => { const result = await api.post('/settings/Accounts_ManuallyApproveNewUsers', { value: true }); await expect(result.ok()).toBeTruthy(); }); + test.beforeEach(async ({ page }) => { poRegistration = new Registration(page); @@ -97,7 +103,7 @@ test.describe.parallel('register', () => { await expect(result.ok()).toBeTruthy(); }); - test('it should expect to have a textbox asking the reason for the registration', async () => { + test('should expect to have a textbox asking the reason for the registration', async () => { await test.step('expect to have a textbox asking the reason for the registration', async () => { await expect(poRegistration.inputReason).toBeVisible(); }); @@ -119,7 +125,7 @@ test.describe.parallel('register', () => { await api.post('/settings/Accounts_RegistrationForm', { value: 'Public' }); }); - test('It should expect a message warning that registration is disabled', async ({ page }) => { + test('should expect a message warning that registration is disabled', async ({ page }) => { await page.goto('/home'); await poRegistration.goToRegister.click(); await expect(poRegistration.registrationDisabledCallout).toBeVisible(); @@ -136,6 +142,36 @@ test.describe.parallel('register', () => { }); }); + test.describe('Registration form validation', async () => { + test.beforeEach(async ({ page }) => { + poRegistration = new Registration(page); + poUtils = new Utils(page); + }); + + test('should not allow registration with an already registered email', async ({ page }) => { + const email = faker.internet.email(); + await request.post('/api/v1/users.register').set('Content-Type', 'application/json').send({ + name: faker.person.firstName(), + email, + username: faker.internet.userName(), + pass: 'any_password', + }); + + await test.step('Attempt registration with the same email', async () => { + await page.goto('/home'); + await poRegistration.goToRegister.click(); + await poRegistration.inputName.fill(faker.person.firstName()); + await poRegistration.inputEmail.fill(email); + await poRegistration.username.fill(faker.internet.userName()); + await poRegistration.inputPassword.fill('any_password'); + await poRegistration.inputPasswordConfirm.fill('any_password'); + await poRegistration.btnRegister.click(); + + await expect(page.getByRole('alert').filter({ hasText: 'Email already exists' })).toBeVisible(); + }); + }); + }); + test.describe('Registration for secret password', async () => { test.beforeEach(async ({ api, page }) => { poRegistration = new Registration(page); @@ -150,7 +186,7 @@ test.describe.parallel('register', () => { await expect(result.ok()).toBeTruthy(); }); - test('It should expect a message warning that registration is disabled', async ({ page }) => { + test('should expect a message warning that registration is disabled', async ({ page }) => { await page.goto('/home'); await poRegistration.goToRegister.click(); await expect(poRegistration.registrationDisabledCallout).toBeVisible(); @@ -160,14 +196,15 @@ test.describe.parallel('register', () => { test.beforeEach(async ({ page }) => { await page.goto('/register/invalid_secret'); }); - test('It should expect a invalid page informing that the secret password is invalid', async ({ page }) => { + + test('should expect a invalid page informing that the secret password is invalid', async ({ page }) => { await expect(page.locator('role=heading[level=2][name="The URL provided is invalid."]')).toBeVisible({ timeout: 10000, }); }); }); - test('It should register a user if the right secret password is provided', async ({ page }) => { + test('should register a user if the right secret password is provided', async ({ page }) => { await page.goto('/register/secret'); await page.waitForSelector('role=form'); await poRegistration.inputName.fill(faker.person.firstName()); @@ -187,7 +224,7 @@ test.describe.parallel('register', () => { await expect(result.ok()).toBeTruthy(); }); - test('It should show an invalid page informing that the url is not valid', async ({ page }) => { + test('should show an invalid page informing that the url is not valid', async ({ page }) => { await page.goto('/register/secret'); await page.waitForSelector('role=heading[level=2]'); await expect(page.locator('role=heading[level=2][name="The URL provided is invalid."]')).toBeVisible(); diff --git a/packages/web-ui-registration/src/RegisterForm.tsx b/packages/web-ui-registration/src/RegisterForm.tsx index dcf32c72d4180..9b7f3e548d3eb 100644 --- a/packages/web-ui-registration/src/RegisterForm.tsx +++ b/packages/web-ui-registration/src/RegisterForm.tsx @@ -158,7 +158,7 @@ export const RegisterForm = ({ setLoginRoute }: { setLoginRoute: DispatchLoginRo /> {errors.name && ( - + {errors.name.message} )} @@ -185,7 +185,7 @@ export const RegisterForm = ({ setLoginRoute }: { setLoginRoute: DispatchLoginRo /> {errors.email && ( - + {errors.email.message} )} @@ -208,7 +208,7 @@ export const RegisterForm = ({ setLoginRoute }: { setLoginRoute: DispatchLoginRo /> {errors.username && ( - + {errors.username.message} )} @@ -232,7 +232,7 @@ export const RegisterForm = ({ setLoginRoute }: { setLoginRoute: DispatchLoginRo /> {errors?.password && ( - + {errors.password.message} )} @@ -260,7 +260,7 @@ export const RegisterForm = ({ setLoginRoute }: { setLoginRoute: DispatchLoginRo /> {errors.passwordConfirmation && ( - + {errors.passwordConfirmation.message} )} @@ -284,7 +284,7 @@ export const RegisterForm = ({ setLoginRoute }: { setLoginRoute: DispatchLoginRo /> {errors.reason && ( - + {errors.reason.message} )}