diff --git a/apps/meteor/app/api/server/v1/misc.ts b/apps/meteor/app/api/server/v1/misc.ts
index 7f085a6eab7f..5f52304da614 100644
--- a/apps/meteor/app/api/server/v1/misc.ts
+++ b/apps/meteor/app/api/server/v1/misc.ts
@@ -30,6 +30,7 @@ import { getUserInfo } from '../helpers/getUserInfo';
import { getPaginationItems } from '../helpers/getPaginationItems';
import { getUserFromParams } from '../helpers/getUserFromParams';
import { i18n } from '../../../../server/lib/i18n';
+import { apiDeprecationLogger } from '../../../lib/server/lib/deprecationWarningLogger';
/**
* @openapi
@@ -392,7 +393,7 @@ API.v1.addRoute(
API.v1.addRoute(
'pw.getPolicy',
{
- authRequired: true,
+ authRequired: false,
},
{
get() {
@@ -409,6 +410,7 @@ API.v1.addRoute(
},
{
async get() {
+ apiDeprecationLogger.endpoint(this.request.route, '7.0.0', this.response, ' Use pw.getPolicy instead.');
check(
this.queryParams,
Match.ObjectIncluding({
diff --git a/apps/meteor/client/views/account/profile/AccountProfileForm.tsx b/apps/meteor/client/views/account/profile/AccountProfileForm.tsx
index ac289f92d960..aa0b23849ae1 100644
--- a/apps/meteor/client/views/account/profile/AccountProfileForm.tsx
+++ b/apps/meteor/client/views/account/profile/AccountProfileForm.tsx
@@ -1,21 +1,10 @@
/* eslint-disable @typescript-eslint/no-non-null-assertion */
import type { IUser } from '@rocket.chat/core-typings';
-import {
- Field,
- FieldGroup,
- TextInput,
- TextAreaInput,
- Box,
- Icon,
- AnimatedVisibility,
- PasswordInput,
- Button,
- Grid,
- Margins,
-} from '@rocket.chat/fuselage';
+import { Field, FieldGroup, TextInput, TextAreaInput, Box, Icon, PasswordInput, Button } from '@rocket.chat/fuselage';
import { useDebouncedCallback, useSafely } from '@rocket.chat/fuselage-hooks';
+import { PasswordVerifier } from '@rocket.chat/ui-client';
import type { TranslationKey } from '@rocket.chat/ui-contexts';
-import { useToastMessageDispatch, useTranslation, useEndpoint } from '@rocket.chat/ui-contexts';
+import { useVerifyPassword, useToastMessageDispatch, useTranslation, useEndpoint } from '@rocket.chat/ui-contexts';
import type { Dispatch, ReactElement, SetStateAction } from 'react';
import React, { useCallback, useMemo, useEffect, useState } from 'react';
@@ -70,6 +59,8 @@ const AccountProfileForm = ({ values, handlers, user, settings, onSaveStateChang
const { realname, email, username, password, confirmationPassword, statusText, bio, statusType, customFields, nickname } =
values as AccountFormValues;
+ const passwordVerifications = useVerifyPassword(password);
+
const {
handleRealname,
handleEmail,
@@ -295,92 +286,78 @@ const AccountProfileForm = ({ values, handlers, user, settings, onSaveStateChang
),
[bio, handleBio, bioError, t],
)}
-
-
-
-
- {useMemo(
- () => (
-
- {t('Email')}
-
- }
- disabled={!allowEmailChange}
- />
-
- {!allowEmailChange && {t('Email_Change_Disabled')}}
- {t(emailError as TranslationKey)}
-
- ),
- [t, email, handleEmail, verified, allowEmailChange, emailError],
- )}
- {useMemo(
- () =>
- !verified && (
-
-
-
-
-
- ),
- [verified, t, email, previousEmail, handleSendConfirmationEmail],
- )}
-
-
-
-
- {useMemo(
- () => (
-
- {t('New_password')}
-
- }
- />
-
- {!allowPasswordChange && {t('Password_Change_Disabled')}}
-
- ),
- [t, password, handlePassword, passwordError, allowPasswordChange, showPasswordError],
- )}
- {useMemo(
- () => (
-
-
- {t('Confirm_password')}
-
- }
- />
-
- {passwordError && {showPasswordError ? passwordError : undefined}}
-
-
- ),
- [t, confirmationPassword, handleConfirmationPassword, password, passwordError, showPasswordError],
+ {useMemo(
+ () => (
+
+ {t('Email')}
+
+ }
+ disabled={!allowEmailChange}
+ />
+ {!verified && (
+
)}
-
-
-
-
+
+ {!allowEmailChange && {t('Email_Change_Disabled')}}
+ {t(emailError as TranslationKey)}
+
+ ),
+ [t, email, emailError, handleEmail, verified, allowEmailChange, previousEmail, handleSendConfirmationEmail],
+ )}
+ {useMemo(
+ () => (
+
+ {t('New_password')}
+
+ }
+ placeholder={t('Create_a_password')}
+ />
+
+
+ }
+ placeholder={t('Confirm_password')}
+ disabled={!allowPasswordChange}
+ />
+
+ {!allowPasswordChange && {t('Password_Change_Disabled')}}
+ {passwordError && {showPasswordError ? passwordError : undefined}}
+ {passwordVerifications && allowPasswordChange && (
+
+ )}
+
+ ),
+ [
+ t,
+ allowPasswordChange,
+ showPasswordError,
+ passwordError,
+ password,
+ handlePassword,
+ confirmationPassword,
+ handleConfirmationPassword,
+ passwordVerifications,
+ ],
+ )}
);
diff --git a/apps/meteor/packages/rocketchat-i18n/i18n/en.i18n.json b/apps/meteor/packages/rocketchat-i18n/i18n/en.i18n.json
index 21f5891b2387..d0ac90683476 100644
--- a/apps/meteor/packages/rocketchat-i18n/i18n/en.i18n.json
+++ b/apps/meteor/packages/rocketchat-i18n/i18n/en.i18n.json
@@ -1073,7 +1073,8 @@
"Confirm_new_encryption_password": "Confirm new encryption password",
"Confirm_new_password": "Confirm New Password",
"Confirm_New_Password_Placeholder": "Please re-enter new password...",
- "Confirm_password": "Confirm your password",
+ "Confirm_password": "Confirm password",
+ "Confirm_your_password": "Confirm your password",
"Confirmation": "Confirmation",
"Configure_video_conference": "Configure conference call",
"Connect": "Connect",
@@ -2391,6 +2392,13 @@
"get-password-policy-mustContainAtLeastOneNumber": "The password should contain at least one number",
"get-password-policy-mustContainAtLeastOneSpecialCharacter": "The password should contain at least one special character",
"get-password-policy-mustContainAtLeastOneUppercase": "The password should contain at least one uppercase letter",
+ "get-password-policy-minLength-label": "At least {{limit}} characters",
+ "get-password-policy-maxLength-label": "At most {{limit}} characters",
+ "get-password-policy-forbidRepeatingCharactersCount-label": "Max. {{limit}} repeating characters",
+ "get-password-policy-mustContainAtLeastOneLowercase-label": "At least one lowercase letter",
+ "get-password-policy-mustContainAtLeastOneUppercase-label": "At least one uppercase letter",
+ "get-password-policy-mustContainAtLeastOneNumber-label": "At least one number",
+ "get-password-policy-mustContainAtLeastOneSpecialCharacter-label": "At least one symbol",
"get-server-info": "Get Server Info",
"get-server-info_description": "Permission to get server info",
"github_no_public_email": "You don't have any email as public email in your GitHub account",
@@ -3839,6 +3847,7 @@
"Password_History": "Password History",
"Password_History_Amount": "Password History Length",
"Password_History_Amount_Description": "Amount of most recently used passwords to prevent users from reusing.",
+ "Password_must_have": "Password must have:",
"Password_Policy": "Password Policy",
"Password_to_access": "Password to access",
"Passwords_do_not_match": "Passwords do not match",
@@ -5089,7 +5098,6 @@
"Type_your_job_title": "Type your job title",
"Type_your_message": "Type your message",
"Type_your_name": "Type your name",
- "Type_your_new_password": "Type your new password",
"Type_your_password": "Type your password",
"Type_your_username": "Type your username",
"UI_Allow_room_names_with_special_chars": "Allow Special Characters in Room Names",
@@ -5806,6 +5814,7 @@
"Community_Private_apps_limit_exceeded": "Community edition app limit has been exceeded.",
"Theme_match_system": "Match system",
"Join_your_team": "Join your team",
+ "Create_a_password": "Create a password",
"Create_an_account": "Create an account",
"Get_all_apps": "Get all the apps your team needs",
"Workspaces_on_community_edition_trial_on": "Workspaces on Community Edition can have up to 5 marketplace apps and 3 private apps enabled. Start a free Enterprise trial to remove these limits today!",
diff --git a/apps/meteor/tests/end-to-end/api/00-miscellaneous.js b/apps/meteor/tests/end-to-end/api/00-miscellaneous.js
index fb4de93a0246..5b07a0d34b4e 100644
--- a/apps/meteor/tests/end-to-end/api/00-miscellaneous.js
+++ b/apps/meteor/tests/end-to-end/api/00-miscellaneous.js
@@ -675,18 +675,6 @@ describe('miscellaneous', function () {
});
describe('/pw.getPolicy', () => {
- it('should fail if not logged in', (done) => {
- request
- .get(api('pw.getPolicy'))
- .expect('Content-Type', 'application/json')
- .expect(401)
- .expect((res) => {
- expect(res.body).to.have.property('status', 'error');
- expect(res.body).to.have.property('message');
- })
- .end(done);
- });
-
it('should return policies', (done) => {
request
.get(api('pw.getPolicy'))
diff --git a/packages/rest-typings/src/v1/misc.ts b/packages/rest-typings/src/v1/misc.ts
index 228b943c757b..4af37334e287 100644
--- a/packages/rest-typings/src/v1/misc.ts
+++ b/packages/rest-typings/src/v1/misc.ts
@@ -206,7 +206,7 @@ export type MiscEndpoints = {
'/v1/pw.getPolicy': {
GET: () => {
enabled: boolean;
- policy: [name: string, options?: Record][];
+ policy: [name: string, value?: Record][];
};
};
diff --git a/packages/ui-client/src/components/PasswordVerifier.tsx b/packages/ui-client/src/components/PasswordVerifier.tsx
new file mode 100644
index 000000000000..e564c1668b5c
--- /dev/null
+++ b/packages/ui-client/src/components/PasswordVerifier.tsx
@@ -0,0 +1,56 @@
+import { Box, Icon } from '@rocket.chat/fuselage';
+import type { useVerifyPassword } from '@rocket.chat/ui-contexts';
+import { useTranslation } from 'react-i18next';
+
+type PasswordVerifierProps = {
+ password: string;
+ passwordVerifications: ReturnType;
+};
+
+export const PasswordVerifier = ({ password, passwordVerifications }: PasswordVerifierProps) => {
+ const { t } = useTranslation();
+
+ const handleRenderPasswordVerification = (passwordVerifications: ReturnType) => {
+ const verifications = [];
+
+ if (!passwordVerifications) return null;
+
+ for (const verification in passwordVerifications) {
+ if (passwordVerifications[verification]) {
+ const { isValid, limit } = passwordVerifications[verification];
+ verifications.push(
+
+
+ {t(`${verification}-label`, { limit })}
+ ,
+ );
+ }
+ }
+
+ return verifications;
+ };
+
+ return (
+
+
+ {t('Password_must_have')}
+
+
+ {handleRenderPasswordVerification(passwordVerifications)}
+
+
+ );
+};
diff --git a/packages/ui-client/src/components/index.ts b/packages/ui-client/src/components/index.ts
index 730775d1d41b..9aa37f5479eb 100644
--- a/packages/ui-client/src/components/index.ts
+++ b/packages/ui-client/src/components/index.ts
@@ -1,6 +1,7 @@
export * from './EmojiPicker';
export * from './ExternalLink';
export * from './DotLeader';
+export * from './PasswordVerifier';
export { default as TextSeparator } from './TextSeparator';
export * from './TooltipComponent';
export * as UserStatus from './UserStatus';
diff --git a/packages/ui-contexts/src/hooks/usePasswordPolicy.ts b/packages/ui-contexts/src/hooks/usePasswordPolicy.ts
new file mode 100644
index 000000000000..ca259a215586
--- /dev/null
+++ b/packages/ui-contexts/src/hooks/usePasswordPolicy.ts
@@ -0,0 +1,9 @@
+import { useQuery } from '@tanstack/react-query';
+
+import { useEndpoint } from './useEndpoint';
+
+export const usePasswordPolicy = () => {
+ const getPasswordPolicy = useEndpoint('GET', '/v1/pw.getPolicy');
+
+ return useQuery(['login', 'password-policy'], async () => getPasswordPolicy());
+};
diff --git a/packages/ui-contexts/src/hooks/useVerifyPassword.ts b/packages/ui-contexts/src/hooks/useVerifyPassword.ts
new file mode 100644
index 000000000000..bb7f134304a8
--- /dev/null
+++ b/packages/ui-contexts/src/hooks/useVerifyPassword.ts
@@ -0,0 +1,59 @@
+import { usePasswordPolicy } from './usePasswordPolicy';
+
+export const useVerifyPassword = (password?: string) => {
+ const { data, isLoading } = usePasswordPolicy();
+
+ if (isLoading) return;
+
+ if (!data?.enabled || password === undefined) return;
+
+ const handleRepeatingChars = (maxRepeatingChars?: number) => {
+ const repeatingCharsHash = {} as Record;
+
+ for (let i = 0; i < password.length; i++) {
+ const currentChar = password[i];
+
+ if (repeatingCharsHash[currentChar]) {
+ repeatingCharsHash[currentChar]++;
+ if (repeatingCharsHash[currentChar] === maxRepeatingChars) return false;
+ } else {
+ repeatingCharsHash[currentChar] = 1;
+ }
+ }
+
+ return true;
+ };
+
+ const passwordVerificationsTemplate: Record boolean> = {
+ 'get-password-policy-minLength': (minLength?: number) => Boolean(minLength && password.length >= minLength),
+ 'get-password-policy-maxLength': (maxLength?: number) => Boolean(maxLength && password.length <= maxLength),
+ 'get-password-policy-forbidRepeatingCharactersCount': handleRepeatingChars,
+ 'get-password-policy-mustContainAtLeastOneLowercase': () => /[a-z]/.test(password),
+ 'get-password-policy-mustContainAtLeastOneUppercase': () => /[A-Z]/.test(password),
+ 'get-password-policy-mustContainAtLeastOneNumber': () => /[0-9]/.test(password),
+ 'get-password-policy-mustContainAtLeastOneSpecialCharacter': () => /[^A-Za-z0-9\s]/.test(password),
+ };
+
+ const passwordVerifications = {} as Record;
+
+ data?.policy.forEach((currentPolicy) => {
+ if (!Array.isArray(currentPolicy)) return;
+
+ if (currentPolicy[0] === 'get-password-policy-forbidRepeatingCharacters') return;
+
+ if (currentPolicy[1]) {
+ passwordVerifications[currentPolicy[0]] = {
+ isValid: passwordVerificationsTemplate[currentPolicy[0]](Object.values(currentPolicy[1])[0]),
+ limit: Object.values(currentPolicy[1])[0],
+ };
+ return;
+ }
+
+ passwordVerifications[currentPolicy[0]] = {
+ isValid: passwordVerificationsTemplate[currentPolicy[0]](),
+ limit: undefined,
+ };
+ });
+
+ return passwordVerifications;
+};
diff --git a/packages/ui-contexts/src/index.ts b/packages/ui-contexts/src/index.ts
index 31cc3c5a35a1..771a34eed36d 100644
--- a/packages/ui-contexts/src/index.ts
+++ b/packages/ui-contexts/src/index.ts
@@ -79,6 +79,8 @@ export { useUserRoom } from './hooks/useUserRoom';
export { useUserSubscription } from './hooks/useUserSubscription';
export { useUserSubscriptionByName } from './hooks/useUserSubscriptionByName';
export { useUserSubscriptions } from './hooks/useUserSubscriptions';
+export { usePasswordPolicy } from './hooks/usePasswordPolicy';
+export { useVerifyPassword } from './hooks/useVerifyPassword';
export { useSelectedDevices } from './hooks/useSelectedDevices';
export { useDeviceConstraints } from './hooks/useDeviceConstraints';
export { useAvailableDevices } from './hooks/useAvailableDevices';
diff --git a/packages/web-ui-registration/package.json b/packages/web-ui-registration/package.json
index bba768ce6d8a..fdd007d1cacb 100644
--- a/packages/web-ui-registration/package.json
+++ b/packages/web-ui-registration/package.json
@@ -4,6 +4,7 @@
"private": true,
"devDependencies": {
"@rocket.chat/layout": "next",
+ "@rocket.chat/ui-client": "workspace:^",
"@rocket.chat/ui-contexts": "workspace:^",
"@tanstack/react-query": "^4.16.1",
"@testing-library/react": "^13.3.0",
diff --git a/packages/web-ui-registration/src/RegisterForm.tsx b/packages/web-ui-registration/src/RegisterForm.tsx
index f2a880a7db6b..8870832991e4 100644
--- a/packages/web-ui-registration/src/RegisterForm.tsx
+++ b/packages/web-ui-registration/src/RegisterForm.tsx
@@ -1,7 +1,8 @@
import { useUniqueId } from '@rocket.chat/fuselage-hooks';
import { FieldGroup, TextInput, Field, PasswordInput, ButtonGroup, Button, TextAreaInput } from '@rocket.chat/fuselage';
import { Form, ActionLink } from '@rocket.chat/layout';
-import { useSetting, useToastMessageDispatch } from '@rocket.chat/ui-contexts';
+import { useSetting, useVerifyPassword, useToastMessageDispatch } from '@rocket.chat/ui-contexts';
+import { PasswordVerifier } from '@rocket.chat/ui-client';
import type { ReactElement } from 'react';
import { useForm } from 'react-hook-form';
import { Trans, useTranslation } from 'react-i18next';
@@ -19,6 +20,7 @@ type LoginRegisterPayload = {
reason: string;
};
+// eslint-disable-next-line complexity
export const RegisterForm = ({ setLoginRoute }: { setLoginRoute: DispatchLoginRouter }): ReactElement => {
const { t } = useTranslation();
@@ -45,6 +47,8 @@ export const RegisterForm = ({ setLoginRoute }: { setLoginRoute: DispatchLoginRo
formState: { errors },
} = useForm();
+ const passwordVerifications = useVerifyPassword(watch('password'));
+
const handleRegister = async ({ password, passwordConfirmation: _, ...formData }: LoginRegisterPayload) => {
registerUser.mutate(
{ pass: password, ...formData },
@@ -98,6 +102,7 @@ export const RegisterForm = ({ setLoginRoute }: { setLoginRoute: DispatchLoginRo
})}
error={errors.name && t('registration.component.form.requiredField')}
aria-invalid={errors.name ? 'true' : 'false'}
+ placeholder={t('onboarding.form.adminInfoForm.fields.fullName.placeholder')}
id='name'
/>
@@ -149,14 +154,10 @@ export const RegisterForm = ({ setLoginRoute }: { setLoginRoute: DispatchLoginRo
error={errors.password && (errors.password?.message || t('registration.component.form.requiredField'))}
aria-invalid={errors.password ? 'true' : undefined}
id='password'
- placeholder={passwordPlaceholder}
+ placeholder={passwordPlaceholder || t('Create_a_password')}
/>
- {errors.password && {errors.password.message}}
-
- {requiresPasswordConfirmation && (
-
- {t('registration.component.form.confirmPassword')}*
+ {requiresPasswordConfirmation && (
- {errors.passwordConfirmation?.type === 'validate' && (
- {t('registration.component.form.invalidConfirmPass')}
- )}
- {errors.passwordConfirmation?.type === 'required' && (
- {t('registration.component.form.requiredField')}
- )}
-
- )}
+ )}
+ {errors.passwordConfirmation?.type === 'validate' && requiresPasswordConfirmation && (
+ {t('registration.component.form.invalidConfirmPass')}
+ )}
+ {errors.passwordConfirmation?.type === 'required' && requiresPasswordConfirmation && (
+ {t('registration.component.form.requiredField')}
+ )}
+ {passwordVerifications && }
+
{manuallyApproveNewUsersRequired && (
{t('registration.component.form.reasonToJoin')}*
diff --git a/packages/web-ui-registration/src/ResetPassword/ResetPasswordPage.tsx b/packages/web-ui-registration/src/ResetPassword/ResetPasswordPage.tsx
index c8d13360eb0e..c392f1376ac8 100644
--- a/packages/web-ui-registration/src/ResetPassword/ResetPasswordPage.tsx
+++ b/packages/web-ui-registration/src/ResetPassword/ResetPasswordPage.tsx
@@ -1,12 +1,21 @@
-import { Button, Field, Modal, Box, PasswordInput, InputBoxSkeleton } from '@rocket.chat/fuselage';
+import { Button, Field, Modal, PasswordInput } from '@rocket.chat/fuselage';
import type { TranslationKey } from '@rocket.chat/ui-contexts';
-import { useRouteParameter, useRoute, useUser, useMethod, useTranslation, useLoginWithToken } from '@rocket.chat/ui-contexts';
+import {
+ useSetting,
+ useVerifyPassword,
+ useRouteParameter,
+ useRoute,
+ useUser,
+ useMethod,
+ useTranslation,
+ useLoginWithToken,
+} from '@rocket.chat/ui-contexts';
import type { ReactElement } from 'react';
import { Form } from '@rocket.chat/layout';
import { useForm } from 'react-hook-form';
+import { PasswordVerifier } from '@rocket.chat/ui-client';
import HorizontalTemplate from '../template/HorizontalTemplate';
-import { usePasswordPolicy } from '../hooks/usePasswordPolicy';
const getChangePasswordReason = ({
requirePasswordChange,
@@ -20,9 +29,7 @@ const ResetPasswordPage = (): ReactElement => {
const resetPassword = useMethod('resetPassword');
const token = useRouteParameter('token');
- const policies = usePasswordPolicy({
- token: user ? undefined : token,
- });
+ const requiresPasswordConfirmation = useSetting('Accounts_RequirePasswordConfirmation');
const homeRouter = useRoute('home');
@@ -36,12 +43,16 @@ const ResetPasswordPage = (): ReactElement => {
setError,
formState: { errors },
formState,
+ watch,
} = useForm<{
password: string;
+ passwordConfirmation: string;
}>({
mode: 'onChange',
});
+ const passwordVerifications = useVerifyPassword(watch('password'));
+
const submit = handleSubmit(async (data) => {
try {
if (token) {
@@ -61,7 +72,7 @@ const ResetPasswordPage = (): ReactElement => {
- {t('Password')}
+ {t('Reset_password')}
@@ -74,22 +85,28 @@ const ResetPasswordPage = (): ReactElement => {
error={errors.password?.message}
aria-invalid={errors.password ? 'true' : 'false'}
id='password'
- placeholder={t('Type_your_new_password')}
+ placeholder={t('Create_a_password')}
name='password'
autoComplete='off'
/>
+ {requiresPasswordConfirmation && (
+
+ watch('password') === val,
+ })}
+ error={errors.passwordConfirmation?.type === 'validate' ? t('registration.component.form.invalidConfirmPass') : undefined}
+ aria-invalid={errors.passwordConfirmation ? 'true' : false}
+ id='passwordConfirmation'
+ placeholder={t('Confirm_password')}
+ />
+
+ )}
{errors && {errors.password?.message}}
-
- {policies.isLoading && }
- {policies.isSuccess &&
- policies.data.enabled &&
- policies.data.policy?.map((policy, index) => (
-
- {t(...(policy as unknown as [name: TranslationKey, options?: Record]))}
-
- ))}
-
+ {passwordVerifications && }
diff --git a/packages/web-ui-registration/src/hooks/usePasswordPolicy.ts b/packages/web-ui-registration/src/hooks/usePasswordPolicy.ts
deleted file mode 100644
index 7b0d0401bf47..000000000000
--- a/packages/web-ui-registration/src/hooks/usePasswordPolicy.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-import { useEndpoint } from '@rocket.chat/ui-contexts';
-import { useQuery } from '@tanstack/react-query';
-
-export const usePasswordPolicy = ({ token }: { token?: string }) => {
- const getPasswordPolicy = useEndpoint('GET', '/v1/pw.getPolicy');
- const getPasswordPolicyRest = useEndpoint('GET', '/v1/pw.getPolicyReset');
-
- return useQuery(['login', 'password-policy', token], async () => (!token ? getPasswordPolicy() : getPasswordPolicyRest({ token })));
-};
diff --git a/password-verification-front.md b/password-verification-front.md
new file mode 100644
index 000000000000..3df0799752bf
--- /dev/null
+++ b/password-verification-front.md
@@ -0,0 +1,9 @@
+---
+"@rocket.chat/meteor": patch
+"@rocket.chat/rest-typings": patch
+"@rocket.chat/ui-client": patch
+"@rocket.chat/ui-contexts": patch
+"@rocket.chat/web-ui-registration": patch
+---
+
+Implemented a visual password verification in the Register User form, My Profile page, and reset password page. With this, the user will know exactly why their password is weak and how to improve it.
\ No newline at end of file
diff --git a/yarn.lock b/yarn.lock
index 4b6079b24185..2f88c99b10e6 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -11116,6 +11116,7 @@ __metadata:
resolution: "@rocket.chat/web-ui-registration@workspace:packages/web-ui-registration"
dependencies:
"@rocket.chat/layout": next
+ "@rocket.chat/ui-client": "workspace:^"
"@rocket.chat/ui-contexts": "workspace:^"
"@tanstack/react-query": ^4.16.1
"@testing-library/react": ^13.3.0