From ca54ada017e97880498d7f64721d26d95601e3f0 Mon Sep 17 00:00:00 2001 From: Julio Araujo Date: Wed, 31 Dec 2025 16:22:03 +0100 Subject: [PATCH 1/9] chore: enable password policy by default and update minlength --- apps/meteor/server/settings/accounts.ts | 4 +- .../views/setupWizard/steps/AdminInfoStep.tsx | 60 ++++++++++++++++++- 2 files changed, 59 insertions(+), 5 deletions(-) diff --git a/apps/meteor/server/settings/accounts.ts b/apps/meteor/server/settings/accounts.ts index 29278e59592a3..9860fb3b26455 100644 --- a/apps/meteor/server/settings/accounts.ts +++ b/apps/meteor/server/settings/accounts.ts @@ -809,7 +809,7 @@ export const createAccountSettings = () => }); await this.section('Password_Policy', async function () { - await this.add('Accounts_Password_Policy_Enabled', false, { + await this.add('Accounts_Password_Policy_Enabled', true, { type: 'boolean', public: true, }); @@ -820,7 +820,7 @@ export const createAccountSettings = () => public: true, }; - await this.add('Accounts_Password_Policy_MinLength', 7, { + await this.add('Accounts_Password_Policy_MinLength', 14, { type: 'int', public: true, enableQuery, diff --git a/packages/ui-client/src/views/setupWizard/steps/AdminInfoStep.tsx b/packages/ui-client/src/views/setupWizard/steps/AdminInfoStep.tsx index 747bbe02ac681..3bf18066b9ea6 100644 --- a/packages/ui-client/src/views/setupWizard/steps/AdminInfoStep.tsx +++ b/packages/ui-client/src/views/setupWizard/steps/AdminInfoStep.tsx @@ -1,7 +1,8 @@ import { AdminInfoPage } from '@rocket.chat/onboarding-ui'; import { escapeRegExp } from '@rocket.chat/string-helpers'; -import { useSetting } from '@rocket.chat/ui-contexts'; +import { useSetting, useVerifyPassword, usePasswordPolicy } from '@rocket.chat/ui-contexts'; import type { ReactElement, ComponentProps } from 'react'; +import { useMemo } from 'react'; import { I18nextProvider, useTranslation } from 'react-i18next'; import { useSetupWizardContext } from '../contexts/SetupWizardContext'; @@ -18,6 +19,46 @@ const AdminInfoStep = (): ReactElement => { const { currentStep, validateEmail, registerAdminUser, maxSteps } = useSetupWizardContext(); + const enabled = useSetting('Accounts_Password_Policy_Enabled', false); + const minLength = useSetting('Accounts_Password_Policy_MinLength', 14); + const maxLength = useSetting('Accounts_Password_Policy_MaxLength', -1); + const forbidRepeatingCharacters = useSetting('Accounts_Password_Policy_ForbidRepeatingCharacters', true); + const forbidRepeatingCharactersCount = useSetting('Accounts_Password_Policy_ForbidRepeatingCharactersCount', 3); + const mustContainAtLeastOneLowercase = useSetting('Accounts_Password_Policy_AtLeastOneLowercase', true); + const mustContainAtLeastOneUppercase = useSetting('Accounts_Password_Policy_AtLeastOneUppercase', true); + const mustContainAtLeastOneNumber = useSetting('Accounts_Password_Policy_AtLeastOneNumber', true); + const mustContainAtLeastOneSpecialCharacter = useSetting('Accounts_Password_Policy_AtLeastOneSpecialCharacter', true); + + const validatePasswordPolicy = usePasswordPolicy({ + enabled, + minLength, + maxLength, + forbidRepeatingCharacters, + forbidRepeatingCharactersCount, + mustContainAtLeastOneLowercase, + mustContainAtLeastOneUppercase, + mustContainAtLeastOneNumber, + mustContainAtLeastOneSpecialCharacter, + throwError: false, + }); + + const passwordPolicyValidations = useVerifyPassword(''); + const passwordRulesHint = useMemo(() => { + if (!passwordPolicyValidations.validations || passwordPolicyValidations.validations.length === 0) { + return ''; + } + + return passwordPolicyValidations.validations + .map((validation) => { + const labelKey = `${validation.name}-label` as const; + if ('limit' in validation) { + return t(labelKey, { limit: validation.limit }); + } + return t(labelKey); + }) + .join(', '); + }, [passwordPolicyValidations.validations, t]); + // TODO: check if username exists const validateUsername = (username: string): boolean | string => { if (!usernameRegExp.test(username) || hasBlockedName(username)) { @@ -27,6 +68,19 @@ const AdminInfoStep = (): ReactElement => { return true; }; + const validatePassword = (password: string): boolean | string => { + if (!password || password.length === 0) { + return t('Required_field', { field: t('Password') }); + } + + const passwordValidation = validatePasswordPolicy(password); + if (!passwordValidation.valid) { + return t('Password_must_meet_the_complexity_requirements'); + } + + return true; + }; + const handleSubmit: ComponentProps['onSubmit'] = async (data) => { registerAdminUser(data); }; @@ -34,8 +88,8 @@ const AdminInfoStep = (): ReactElement => { return ( password.length > 0} - passwordRulesHint='' + validatePassword={validatePassword} + passwordRulesHint={passwordRulesHint} validateUsername={validateUsername} validateEmail={validateEmail} currentStep={currentStep} From 69f7e4c97ae3ef422ef7bf2f27a9d404c95f940e Mon Sep 17 00:00:00 2001 From: Julio Araujo Date: Wed, 31 Dec 2025 16:31:12 +0100 Subject: [PATCH 2/9] Add changeset --- .changeset/nervous-clouds-carry.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/nervous-clouds-carry.md diff --git a/.changeset/nervous-clouds-carry.md b/.changeset/nervous-clouds-carry.md new file mode 100644 index 0000000000000..fcb81addfb3c8 --- /dev/null +++ b/.changeset/nervous-clouds-carry.md @@ -0,0 +1,6 @@ +--- +'@rocket.chat/ui-client': minor +'@rocket.chat/meteor': minor +--- + +Enables the password policy by default to ensure security by default and alters SetupWizard to handle errors From 78b1886aa5ec47031f6a3e295aec427df9a228d6 Mon Sep 17 00:00:00 2001 From: Julio Araujo Date: Mon, 5 Jan 2026 16:03:04 +0100 Subject: [PATCH 3/9] Change test users' password to comply with password policy --- apps/meteor/tests/end-to-end/api/users.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/meteor/tests/end-to-end/api/users.ts b/apps/meteor/tests/end-to-end/api/users.ts index f71efe50402a0..41c116fd1f3ca 100644 --- a/apps/meteor/tests/end-to-end/api/users.ts +++ b/apps/meteor/tests/end-to-end/api/users.ts @@ -817,7 +817,7 @@ describe('[Users]', () => { email, name: 'name', username, - pass: 'test', + pass: 'P@ssw0rd1234.!', }) .expect('Content-Type', 'application/json') .expect(200) @@ -838,7 +838,7 @@ describe('[Users]', () => { email, name: 'name', username: 'test$username<>', - pass: 'test', + pass: 'P@ssw0rd1234.!', }) .expect('Content-Type', 'application/json') .expect(400) @@ -856,7 +856,7 @@ describe('[Users]', () => { email, name: 'name', username, - pass: 'test', + pass: 'P@ssw0rd1234.!', }) .expect('Content-Type', 'application/json') .expect(400) @@ -873,7 +873,7 @@ describe('[Users]', () => { email, name: '', username, - pass: 'test', + pass: 'P@ssw0rd1234.!', }) .expect('Content-Type', 'application/json') .expect(400) From b1d3213e6a488d1b82050d15174939ecdcdb1578 Mon Sep 17 00:00:00 2001 From: Julio Araujo Date: Tue, 6 Jan 2026 00:33:26 +0100 Subject: [PATCH 4/9] alter tests to reflect new password policy --- apps/meteor/tests/e2e/account-security.spec.ts | 10 +++++++++- apps/meteor/tests/e2e/administration.spec.ts | 8 ++++---- apps/meteor/tests/e2e/register.spec.ts | 18 +++++++++--------- 3 files changed, 22 insertions(+), 14 deletions(-) diff --git a/apps/meteor/tests/e2e/account-security.spec.ts b/apps/meteor/tests/e2e/account-security.spec.ts index e4ab3b3cc053e..7bebb74ea3c3d 100644 --- a/apps/meteor/tests/e2e/account-security.spec.ts +++ b/apps/meteor/tests/e2e/account-security.spec.ts @@ -8,7 +8,15 @@ import { test, expect } from './utils/test'; test.use({ storageState: Users.admin.state }); -const RANDOM_PASSWORD = faker.string.alphanumeric(10); +const RANDOM_PASSWORD = faker.helpers + .shuffle([ + faker.string.alpha({ casing: 'upper' }), + faker.string.alpha({ casing: 'lower' }), + faker.string.numeric(), + faker.string.symbol(), + faker.string.alphanumeric(10), + ]) + .join(''); test.describe.serial('account-security', () => { let poAccountSecurity: AccountSecurity; diff --git a/apps/meteor/tests/e2e/administration.spec.ts b/apps/meteor/tests/e2e/administration.spec.ts index 5769794f13f3d..30c26ead2815d 100644 --- a/apps/meteor/tests/e2e/administration.spec.ts +++ b/apps/meteor/tests/e2e/administration.spec.ts @@ -75,8 +75,8 @@ test.describe.parallel('administration', () => { await poAdminUsers.editUser.inputName.fill(faker.person.firstName()); await poAdminUsers.editUser.inputUserName.fill(faker.internet.userName()); await poAdminUsers.editUser.inputSetManually.click(); - await poAdminUsers.editUser.inputPassword.fill('any_password'); - await poAdminUsers.editUser.inputConfirmPassword.fill('any_password'); + await poAdminUsers.editUser.inputPassword.fill('P@ssw0rd1234.!'); + await poAdminUsers.editUser.inputConfirmPassword.fill('P@ssw0rd1234.!'); await expect(poAdminUsers.editUser.userRole).toBeVisible(); await poAdminUsers.editUser.btnAddUser.click(); }); @@ -95,8 +95,8 @@ test.describe.parallel('administration', () => { await poAdminUsers.editUser.inputUserName.type(username); await poAdminUsers.editUser.inputEmail.type(faker.internet.email()); await poAdminUsers.editUser.inputSetManually.click(); - await poAdminUsers.editUser.inputPassword.type('any_password'); - await poAdminUsers.editUser.inputConfirmPassword.type('any_password'); + await poAdminUsers.editUser.inputPassword.type('P@ssw0rd1234.!'); + await poAdminUsers.editUser.inputConfirmPassword.type('P@ssw0rd1234.!'); await expect(poAdminUsers.editUser.userRole).toBeVisible(); await expect(poAdminUsers.editUser.joinDefaultChannels).toBeVisible(); await poAdminUsers.editUser.btnAddUser.click(); diff --git a/apps/meteor/tests/e2e/register.spec.ts b/apps/meteor/tests/e2e/register.spec.ts index 35c402281d87f..1cd36d94c355b 100644 --- a/apps/meteor/tests/e2e/register.spec.ts +++ b/apps/meteor/tests/e2e/register.spec.ts @@ -31,15 +31,15 @@ test.describe.parallel('register', () => { await poRegistration.inputName.fill(faker.person.firstName()); await poRegistration.inputEmail.fill(faker.internet.email()); await poRegistration.username.fill(faker.internet.userName()); - await poRegistration.inputPassword.fill('any_password'); - await poRegistration.inputPasswordConfirm.fill('any_password_2'); + await poRegistration.inputPassword.fill('P@ssw0rd1234.!'); + await poRegistration.inputPasswordConfirm.fill('Password1235.!'); await poRegistration.btnRegister.click(); await expect(poRegistration.inputPasswordConfirm).toBeInvalid(); }); await test.step('expect successfully register a new user', async () => { - await poRegistration.inputPasswordConfirm.fill('any_password'); + await poRegistration.inputPasswordConfirm.fill('P@ssw0rd1234.!'); await poRegistration.btnRegister.click(); await poAuth.waitForDisplay(); }); @@ -74,7 +74,7 @@ test.describe.parallel('register', () => { await poRegistration.inputName.fill(faker.person.firstName()); await poRegistration.inputEmail.fill(faker.internet.email()); await poRegistration.username.fill(faker.internet.userName()); - await poRegistration.inputPassword.fill('any_password'); + await poRegistration.inputPassword.fill('P@ssw0rd1234.!'); await poRegistration.btnRegister.click(); await poAuth.waitForDisplay(); @@ -149,7 +149,7 @@ test.describe.parallel('register', () => { name: faker.person.firstName(), email, username: faker.internet.userName(), - pass: 'any_password', + pass: 'P@ssw0rd1234.!', }); await test.step('Attempt registration with the same email', async () => { @@ -158,8 +158,8 @@ test.describe.parallel('register', () => { 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.inputPassword.fill('P@ssw0rd1234.!'); + await poRegistration.inputPasswordConfirm.fill('P@ssw0rd1234.!'); await poRegistration.btnRegister.click(); await expect(page.getByRole('alert').filter({ hasText: 'Email already exists' })).toBeVisible(); @@ -203,8 +203,8 @@ test.describe.parallel('register', () => { await poRegistration.inputName.fill(faker.person.firstName()); await poRegistration.inputEmail.fill(faker.internet.email()); await poRegistration.username.fill(faker.internet.userName()); - await poRegistration.inputPassword.fill('any_password'); - await poRegistration.inputPasswordConfirm.fill('any_password'); + await poRegistration.inputPassword.fill('P@ssw0rd1234.!'); + await poRegistration.inputPasswordConfirm.fill('P@ssw0rd1234.!'); await poRegistration.btnRegister.click(); await poAuth.waitForDisplay(); }); From e856ee9a68958f96b64899f5f7bdfdf535a0da9c Mon Sep 17 00:00:00 2001 From: Julio Araujo Date: Tue, 6 Jan 2026 01:21:31 +0100 Subject: [PATCH 5/9] Change tests/data/user password to match password policy --- apps/meteor/tests/data/user.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/meteor/tests/data/user.ts b/apps/meteor/tests/data/user.ts index e7bbf03f83a25..2c6658aa38979 100644 --- a/apps/meteor/tests/data/user.ts +++ b/apps/meteor/tests/data/user.ts @@ -1,7 +1,7 @@ import type { Credentials } from '@rocket.chat/api-client'; import type { IUser } from '@rocket.chat/core-typings'; -export const password = 'rocket.chat'; +export const password = 'R0ck3t.ch@tP@ssw0rd1234.!'; export const adminUsername = 'rocketchat.internal.admin.test'; export const adminEmail = `${adminUsername}@rocket.chat`; export const adminPassword = adminUsername; From 292f724599ac0d45bc8e501c37678bdb2d8693f7 Mon Sep 17 00:00:00 2001 From: Julio Araujo Date: Tue, 6 Jan 2026 02:33:59 +0100 Subject: [PATCH 6/9] alter tests to match new password policy --- apps/meteor/tests/end-to-end/api/users.ts | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/apps/meteor/tests/end-to-end/api/users.ts b/apps/meteor/tests/end-to-end/api/users.ts index 41c116fd1f3ca..eb0baf3aa3d00 100644 --- a/apps/meteor/tests/end-to-end/api/users.ts +++ b/apps/meteor/tests/end-to-end/api/users.ts @@ -1105,7 +1105,7 @@ describe('[Users]', () => { email: `me-${Date.now()}@email.com`, name: 'testuser', username: ufsUsername, - password: '1234', + password, }); await request @@ -2171,7 +2171,7 @@ describe('[Users]', () => { .send({ userId: targetUser._id, data: { - password: 'itsnotworking', + password: '1tsn0tw0rkingP@ssw0rd1234.!', }, }) .expect('Content-Type', 'application/json') @@ -2193,7 +2193,7 @@ describe('[Users]', () => { .send({ userId: targetUser._id, data: { - password: 'itsnotworking', + password: '1tsn0tw0rkingP@ssw0rd1234.!', }, }) .expect('Content-Type', 'application/json') @@ -2700,7 +2700,7 @@ describe('[Users]', () => { .set(credentials) .send({ data: { - newPassword: 'the new pass', + newPassword: '1Tsn3wP@ssw0rd1234.!', }, }) .expect('Content-Type', 'application/json') @@ -2851,7 +2851,7 @@ describe('[Users]', () => { .set(credentials) .send({ data: { - newPassword: 'MyNewPassw0rd', + newPassword: '1Tsn3wP@ssw0rd1234.!', }, }) .expect('Content-Type', 'application/json') @@ -2891,13 +2891,11 @@ describe('[Users]', () => { describe('[Password Policy]', () => { before(async () => { await updateSetting('Accounts_AllowPasswordChange', true); - await updateSetting('Accounts_Password_Policy_Enabled', true); await updateSetting('Accounts_TwoFactorAuthentication_Enabled', false); }); after(async () => { await updateSetting('Accounts_AllowPasswordChange', true); - await updateSetting('Accounts_Password_Policy_Enabled', false); await updateSetting('Accounts_TwoFactorAuthentication_Enabled', true); }); @@ -3086,7 +3084,7 @@ describe('[Users]', () => { .send({ data: { currentPassword, - newPassword: '123Abc@!', + newPassword: '1Tsn3wP@ssw0rd1234.!', }, }) .expect('Content-Type', 'application/json') From faad918ba7209a250b8be3dac6fd773433900e18 Mon Sep 17 00:00:00 2001 From: Julio Araujo Date: Tue, 6 Jan 2026 13:50:36 +0100 Subject: [PATCH 7/9] Fix playwright tests --- apps/meteor/tests/e2e/account-security.spec.ts | 4 +++- apps/meteor/tests/e2e/reset-password.spec.ts | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/apps/meteor/tests/e2e/account-security.spec.ts b/apps/meteor/tests/e2e/account-security.spec.ts index 7bebb74ea3c3d..f922cf8cb9935 100644 --- a/apps/meteor/tests/e2e/account-security.spec.ts +++ b/apps/meteor/tests/e2e/account-security.spec.ts @@ -21,10 +21,11 @@ const RANDOM_PASSWORD = faker.helpers test.describe.serial('account-security', () => { let poAccountSecurity: AccountSecurity; - test.beforeEach(async ({ page }) => { + test.beforeEach(async ({ page, api }) => { poAccountSecurity = new AccountSecurity(page); await page.goto('/account/security'); await page.waitForSelector('#main-content'); + await setSettingValueById(api, 'Accounts_Password_Policy_Enabled', false); }); test.afterAll(async ({ api }) => @@ -32,6 +33,7 @@ test.describe.serial('account-security', () => { setSettingValueById(api, 'Accounts_AllowPasswordChange', true), setSettingValueById(api, 'Accounts_TwoFactorAuthentication_Enabled', true), setSettingValueById(api, 'E2E_Enable', false), + setSettingValueById(api, 'Accounts_Password_Policy_Enabled', true), ]), ); diff --git a/apps/meteor/tests/e2e/reset-password.spec.ts b/apps/meteor/tests/e2e/reset-password.spec.ts index fcd26eb4f355e..dfc97db2803cb 100644 --- a/apps/meteor/tests/e2e/reset-password.spec.ts +++ b/apps/meteor/tests/e2e/reset-password.spec.ts @@ -17,8 +17,8 @@ test.describe.parallel('Reset Password', () => { }); test('should confirm password be invalid', async () => { - await poRegistration.inputPassword.fill('123456'); - await poRegistration.inputPasswordConfirm.fill('123455'); + await poRegistration.inputPassword.fill('P@ssw0rd1234.!'); + await poRegistration.inputPasswordConfirm.fill('Password4321.!'); await poRegistration.btnReset.click(); await expect(poRegistration.inputPasswordConfirm).toBeInvalid(); }); From 5e303df09fcda0e0aef5d3059ce41f8bbceace62 Mon Sep 17 00:00:00 2001 From: Julio Araujo Date: Wed, 7 Jan 2026 00:26:49 +0100 Subject: [PATCH 8/9] Fix Playwright tests with new password policy enabled by default --- apps/meteor/tests/e2e/user-required-password-change.spec.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/meteor/tests/e2e/user-required-password-change.spec.ts b/apps/meteor/tests/e2e/user-required-password-change.spec.ts index bf56ec7003804..8d27dc35328c6 100644 --- a/apps/meteor/tests/e2e/user-required-password-change.spec.ts +++ b/apps/meteor/tests/e2e/user-required-password-change.spec.ts @@ -19,6 +19,7 @@ test.describe('User - Password change required', () => { test.beforeAll(async ({ api }) => { settingDefaultValue = await getSettingValueById(api, 'Accounts_RequirePasswordConfirmation'); + await setSettingValueById(api, 'Accounts_Password_Policy_Enabled', false); await setSettingValueById(api, 'Accounts_RequirePasswordConfirmation', true); userRequiringPasswordChange = await createTestUser(api, { data: { requirePasswordChange: true } }); userNotRequiringPasswordChange = await createTestUser(api, { data: { requirePasswordChange: false } }); @@ -38,6 +39,7 @@ test.describe('User - Password change required', () => { userRequiringPasswordChange.delete(), userNotRequiringPasswordChange.delete(), userNotAbleToLogin.delete(), + setSettingValueById(api, 'Accounts_Password_Policy_Enabled', true), ]); }); From 23c4f33859099fec5781f366bd0ec8c61875ea93 Mon Sep 17 00:00:00 2001 From: Julio Araujo Date: Thu, 8 Jan 2026 13:44:52 +0100 Subject: [PATCH 9/9] centralize password policy options --- .../PasswordVerifiers.spec.tsx | 12 +++++++- .../views/setupWizard/steps/AdminInfoStep.tsx | 26 ++--------------- .../src/hooks/usePasswordPolicyOptions.ts | 28 +++++++++++++++++++ .../src/hooks/useVerifyPassword.ts | 26 ++--------------- packages/ui-contexts/src/index.ts | 1 + 5 files changed, 46 insertions(+), 47 deletions(-) create mode 100644 packages/ui-contexts/src/hooks/usePasswordPolicyOptions.ts diff --git a/packages/ui-client/src/components/PasswordVerifier/PasswordVerifiers.spec.tsx b/packages/ui-client/src/components/PasswordVerifier/PasswordVerifiers.spec.tsx index ba91f360efbf5..d69753cb6f293 100644 --- a/packages/ui-client/src/components/PasswordVerifier/PasswordVerifiers.spec.tsx +++ b/packages/ui-client/src/components/PasswordVerifier/PasswordVerifiers.spec.tsx @@ -18,7 +18,17 @@ it('should render no policy if its disabled ', () => { it('should render no policy if its enabled but empty', async () => { render(, { - wrapper: mockAppRoot().build(), + wrapper: mockAppRoot() + .withSetting('Accounts_Password_Policy_Enabled', true) + .withSetting('Accounts_Password_Policy_MinLength', -1) + .withSetting('Accounts_Password_Policy_MaxLength', -1) + .withSetting('Accounts_Password_Policy_ForbidRepeatingCharacters', false) + .withSetting('Accounts_Password_Policy_ForbidRepeatingCharactersCount', 3) + .withSetting('Accounts_Password_Policy_AtLeastOneLowercase', false) + .withSetting('Accounts_Password_Policy_AtLeastOneUppercase', false) + .withSetting('Accounts_Password_Policy_AtLeastOneNumber', false) + .withSetting('Accounts_Password_Policy_AtLeastOneSpecialCharacter', false) + .build(), }); await waitFor(() => { diff --git a/packages/ui-client/src/views/setupWizard/steps/AdminInfoStep.tsx b/packages/ui-client/src/views/setupWizard/steps/AdminInfoStep.tsx index 3bf18066b9ea6..2c198cf2060e8 100644 --- a/packages/ui-client/src/views/setupWizard/steps/AdminInfoStep.tsx +++ b/packages/ui-client/src/views/setupWizard/steps/AdminInfoStep.tsx @@ -1,6 +1,6 @@ import { AdminInfoPage } from '@rocket.chat/onboarding-ui'; import { escapeRegExp } from '@rocket.chat/string-helpers'; -import { useSetting, useVerifyPassword, usePasswordPolicy } from '@rocket.chat/ui-contexts'; +import { useSetting, useVerifyPassword, usePasswordPolicy, usePasswordPolicyOptions } from '@rocket.chat/ui-contexts'; import type { ReactElement, ComponentProps } from 'react'; import { useMemo } from 'react'; import { I18nextProvider, useTranslation } from 'react-i18next'; @@ -19,28 +19,8 @@ const AdminInfoStep = (): ReactElement => { const { currentStep, validateEmail, registerAdminUser, maxSteps } = useSetupWizardContext(); - const enabled = useSetting('Accounts_Password_Policy_Enabled', false); - const minLength = useSetting('Accounts_Password_Policy_MinLength', 14); - const maxLength = useSetting('Accounts_Password_Policy_MaxLength', -1); - const forbidRepeatingCharacters = useSetting('Accounts_Password_Policy_ForbidRepeatingCharacters', true); - const forbidRepeatingCharactersCount = useSetting('Accounts_Password_Policy_ForbidRepeatingCharactersCount', 3); - const mustContainAtLeastOneLowercase = useSetting('Accounts_Password_Policy_AtLeastOneLowercase', true); - const mustContainAtLeastOneUppercase = useSetting('Accounts_Password_Policy_AtLeastOneUppercase', true); - const mustContainAtLeastOneNumber = useSetting('Accounts_Password_Policy_AtLeastOneNumber', true); - const mustContainAtLeastOneSpecialCharacter = useSetting('Accounts_Password_Policy_AtLeastOneSpecialCharacter', true); - - const validatePasswordPolicy = usePasswordPolicy({ - enabled, - minLength, - maxLength, - forbidRepeatingCharacters, - forbidRepeatingCharactersCount, - mustContainAtLeastOneLowercase, - mustContainAtLeastOneUppercase, - mustContainAtLeastOneNumber, - mustContainAtLeastOneSpecialCharacter, - throwError: false, - }); + const passwordPolicyOptions = usePasswordPolicyOptions(); + const validatePasswordPolicy = usePasswordPolicy(passwordPolicyOptions); const passwordPolicyValidations = useVerifyPassword(''); const passwordRulesHint = useMemo(() => { diff --git a/packages/ui-contexts/src/hooks/usePasswordPolicyOptions.ts b/packages/ui-contexts/src/hooks/usePasswordPolicyOptions.ts new file mode 100644 index 0000000000000..47061a11871c4 --- /dev/null +++ b/packages/ui-contexts/src/hooks/usePasswordPolicyOptions.ts @@ -0,0 +1,28 @@ +import type { PasswordPolicyOptions } from '@rocket.chat/password-policies'; + +import { useSetting } from './useSetting'; + +export const usePasswordPolicyOptions = (): PasswordPolicyOptions => { + const enabled = useSetting('Accounts_Password_Policy_Enabled', true); + const minLength = useSetting('Accounts_Password_Policy_MinLength', 14); + const maxLength = useSetting('Accounts_Password_Policy_MaxLength', -1); + const forbidRepeatingCharacters = useSetting('Accounts_Password_Policy_ForbidRepeatingCharacters', true); + const forbidRepeatingCharactersCount = useSetting('Accounts_Password_Policy_ForbidRepeatingCharactersCount', 3); + const mustContainAtLeastOneLowercase = useSetting('Accounts_Password_Policy_AtLeastOneLowercase', true); + const mustContainAtLeastOneUppercase = useSetting('Accounts_Password_Policy_AtLeastOneUppercase', true); + const mustContainAtLeastOneNumber = useSetting('Accounts_Password_Policy_AtLeastOneNumber', true); + const mustContainAtLeastOneSpecialCharacter = useSetting('Accounts_Password_Policy_AtLeastOneSpecialCharacter', true); + + return { + enabled, + minLength, + maxLength, + forbidRepeatingCharacters, + forbidRepeatingCharactersCount, + mustContainAtLeastOneLowercase, + mustContainAtLeastOneUppercase, + mustContainAtLeastOneNumber, + mustContainAtLeastOneSpecialCharacter, + throwError: false, + }; +}; diff --git a/packages/ui-contexts/src/hooks/useVerifyPassword.ts b/packages/ui-contexts/src/hooks/useVerifyPassword.ts index f854f227e7dac..dbfd9e135437d 100644 --- a/packages/ui-contexts/src/hooks/useVerifyPassword.ts +++ b/packages/ui-contexts/src/hooks/useVerifyPassword.ts @@ -1,31 +1,11 @@ import { useMemo } from 'react'; import { usePasswordPolicy, type UsePasswordPolicyReturn } from './usePasswordPolicy'; -import { useSetting } from './useSetting'; +import { usePasswordPolicyOptions } from './usePasswordPolicyOptions'; export const useVerifyPassword: UsePasswordPolicyReturn = (password) => { - const enabled = useSetting('Accounts_Password_Policy_Enabled', false); - const minLength = useSetting('Accounts_Password_Policy_MinLength', 7); - const maxLength = useSetting('Accounts_Password_Policy_MaxLength', -1); - const forbidRepeatingCharacters = useSetting('Accounts_Password_Policy_ForbidRepeatingCharacters', true); - const forbidRepeatingCharactersCount = useSetting('Accounts_Password_Policy_ForbidRepeatingCharactersCount', 3); - const mustContainAtLeastOneLowercase = useSetting('Accounts_Password_Policy_AtLeastOneLowercase', true); - const mustContainAtLeastOneUppercase = useSetting('Accounts_Password_Policy_AtLeastOneUppercase', true); - const mustContainAtLeastOneNumber = useSetting('Accounts_Password_Policy_AtLeastOneNumber', true); - const mustContainAtLeastOneSpecialCharacter = useSetting('Accounts_Password_Policy_AtLeastOneSpecialCharacter', true); - - const validate = usePasswordPolicy({ - enabled, - minLength, - maxLength, - forbidRepeatingCharacters, - forbidRepeatingCharactersCount, - mustContainAtLeastOneLowercase, - mustContainAtLeastOneUppercase, - mustContainAtLeastOneNumber, - mustContainAtLeastOneSpecialCharacter, - throwError: false, - }); + const options = usePasswordPolicyOptions(); + const validate = usePasswordPolicy(options); return useMemo(() => validate(password || ''), [password, validate]); }; diff --git a/packages/ui-contexts/src/index.ts b/packages/ui-contexts/src/index.ts index 612e2bc6c6fdb..bd209b70abb81 100644 --- a/packages/ui-contexts/src/index.ts +++ b/packages/ui-contexts/src/index.ts @@ -88,6 +88,7 @@ export { useUserRoom } from './hooks/useUserRoom'; export { useUserSubscription } from './hooks/useUserSubscription'; export { useUserSubscriptionByName } from './hooks/useUserSubscriptionByName'; export { useUserSubscriptions } from './hooks/useUserSubscriptions'; +export { usePasswordPolicyOptions } from './hooks/usePasswordPolicyOptions'; export { usePasswordPolicy, type PasswordPolicyValidation } from './hooks/usePasswordPolicy'; export { useVerifyPassword } from './hooks/useVerifyPassword'; export { useSelectedDevices } from './hooks/useSelectedDevices';