diff --git a/apps/meteor/app/api/server/v1/channels.ts b/apps/meteor/app/api/server/v1/channels.ts index c81a77c6ef41c..77d19384fe3fe 100644 --- a/apps/meteor/app/api/server/v1/channels.ts +++ b/apps/meteor/app/api/server/v1/channels.ts @@ -466,11 +466,11 @@ API.v1.addRoute( return API.v1.forbidden(); } - const moderators = ( - await Subscriptions.findByRoomIdAndRoles(findResult._id, ['moderator'], { - projection: { u: 1 }, - }).toArray() - ).map((sub: ISubscription) => sub.u); + const moderators = await Subscriptions.findByRoomIdAndRoles(findResult._id, ['moderator'], { + projection: { u: 1, _id: 0 }, + }) + .map((sub) => sub.u) + .toArray(); return API.v1.success({ moderators, diff --git a/apps/meteor/app/api/server/v1/groups.ts b/apps/meteor/app/api/server/v1/groups.ts index 8b9e0ef7b9529..20a11b5268d70 100644 --- a/apps/meteor/app/api/server/v1/groups.ts +++ b/apps/meteor/app/api/server/v1/groups.ts @@ -1215,11 +1215,11 @@ API.v1.addRoute( userId: this.userId, }); - const moderators = ( - await Subscriptions.findByRoomIdAndRoles(findResult.rid, ['moderator'], { - projection: { u: 1 }, - }).toArray() - ).map((sub: any) => sub.u); + const moderators = await Subscriptions.findByRoomIdAndRoles(findResult.rid, ['moderator'], { + projection: { u: 1, _id: 0 }, + }) + .map((sub) => sub.u) + .toArray(); return API.v1.success({ moderators, diff --git a/apps/meteor/app/api/server/v1/roles.ts b/apps/meteor/app/api/server/v1/roles.ts index fc17ed4656221..10c3a9fd78389 100644 --- a/apps/meteor/app/api/server/v1/roles.ts +++ b/apps/meteor/app/api/server/v1/roles.ts @@ -1,7 +1,7 @@ -import { api } from '@rocket.chat/core-services'; +import { api, Authorization } from '@rocket.chat/core-services'; import type { IRole } from '@rocket.chat/core-typings'; import { Roles, Users } from '@rocket.chat/models'; -import { isRoleAddUserToRoleProps, isRoleDeleteProps, isRoleRemoveUserFromRoleProps } from '@rocket.chat/rest-typings'; +import { ajv, isRoleAddUserToRoleProps, isRoleDeleteProps, isRoleRemoveUserFromRoleProps } from '@rocket.chat/rest-typings'; import { check, Match } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; @@ -13,6 +13,7 @@ import { addUserToRole } from '../../../authorization/server/methods/addUserToRo import { apiDeprecationLogger } from '../../../lib/server/lib/deprecationWarningLogger'; import { notifyOnRoleChanged } from '../../../lib/server/lib/notifyListener'; import { settings } from '../../../settings/server/index'; +import type { ExtractRoutesFromAPI } from '../ApiClass'; import { API } from '../api'; import { getPaginationItems } from '../helpers/getPaginationItems'; import { getUserFromParams } from '../helpers/getUserFromParams'; @@ -243,3 +244,43 @@ API.v1.addRoute( }, }, ); + +const rolesRoutes = API.v1.get( + 'roles.getUsersInPublicRoles', + { + authRequired: true, + response: { + 200: ajv.compile<{ + users: { + _id: string; + username: string; + roles: string[]; + }[]; + }>({ + type: 'object', + properties: { + users: { + type: 'array', + items: { + type: 'object', + properties: { _id: { type: 'string' }, username: { type: 'string' }, roles: { type: 'array', items: { type: 'string' } } }, + }, + }, + }, + }), + }, + }, + + async () => { + return API.v1.success({ + users: await Authorization.getUsersFromPublicRoles(), + }); + }, +); + +type RolesEndpoints = ExtractRoutesFromAPI; + +declare module '@rocket.chat/rest-typings' { + // eslint-disable-next-line @typescript-eslint/naming-convention, @typescript-eslint/no-empty-interface + interface Endpoints extends RolesEndpoints {} +} diff --git a/apps/meteor/app/api/server/v1/rooms.ts b/apps/meteor/app/api/server/v1/rooms.ts index 446374d934684..a598e577d93e1 100644 --- a/apps/meteor/app/api/server/v1/rooms.ts +++ b/apps/meteor/app/api/server/v1/rooms.ts @@ -4,6 +4,7 @@ import { isPrivateRoom, isPublicRoom } from '@rocket.chat/core-typings'; import { Messages, Rooms, Users, Uploads, Subscriptions } from '@rocket.chat/models'; import type { Notifications } from '@rocket.chat/rest-typings'; import { + ajv, isGETRoomsNameExists, isRoomsImagesProps, isRoomsMuteUnmuteUserProps, @@ -23,6 +24,7 @@ import * as dataExport from '../../../../server/lib/dataExport'; import { eraseRoom } from '../../../../server/lib/eraseRoom'; import { findUsersOfRoomOrderedByRole } from '../../../../server/lib/findUsersOfRoomOrderedByRole'; import { openRoom } from '../../../../server/lib/openRoom'; +import type { RoomRoles } from '../../../../server/lib/roles/getRoomRoles'; import { hideRoomMethod } from '../../../../server/methods/hideRoom'; import { muteUserInRoom } from '../../../../server/methods/muteUserInRoom'; import { toggleFavoriteMethod } from '../../../../server/methods/toggleFavorite'; @@ -37,12 +39,14 @@ import { sendFileMessage } from '../../../file-upload/server/methods/sendFileMes import { syncRolePrioritiesForRoomIfRequired } from '../../../lib/server/functions/syncRolePrioritiesForRoomIfRequired'; import { executeArchiveRoom } from '../../../lib/server/methods/archiveRoom'; import { cleanRoomHistoryMethod } from '../../../lib/server/methods/cleanRoomHistory'; +import { executeGetRoomRoles } from '../../../lib/server/methods/getRoomRoles'; import { leaveRoomMethod } from '../../../lib/server/methods/leaveRoom'; import { executeUnarchiveRoom } from '../../../lib/server/methods/unarchiveRoom'; import { applyAirGappedRestrictionsValidation } from '../../../license/server/airGappedRestrictionsWrapper'; import type { NotificationFieldType } from '../../../push-notifications/server/methods/saveNotificationSettings'; import { saveNotificationSettingsMethod } from '../../../push-notifications/server/methods/saveNotificationSettings'; import { settings } from '../../../settings/server'; +import type { ExtractRoutesFromAPI } from '../ApiClass'; import { API } from '../api'; import { composeRoomWithLastMessage } from '../helpers/composeRoomWithLastMessage'; import { getPaginationItems } from '../helpers/getPaginationItems'; @@ -1005,3 +1009,62 @@ API.v1.addRoute( }, }, ); + +const isRoomGetRolesPropsSchema = { + type: 'object', + properties: { + rid: { type: 'string' }, + }, + additionalProperties: false, + required: ['rid'], +}; +export const roomEndpoints = API.v1.get( + 'rooms.roles', + { + authRequired: true, + query: ajv.compile<{ + rid: string; + }>(isRoomGetRolesPropsSchema), + response: { + 200: ajv.compile<{ + roles: RoomRoles[]; + }>({ + type: 'object', + properties: { + roles: { + type: 'array', + items: { + type: 'object', + properties: { + rid: { type: 'string' }, + u: { + type: 'object', + properties: { _id: { type: 'string' }, username: { type: 'string' } }, + required: ['_id', 'username'], + }, + roles: { type: 'array', items: { type: 'string' } }, + }, + required: ['rid', 'u', 'roles'], + }, + }, + }, + required: ['roles'], + }), + }, + }, + async function () { + const { rid } = this.queryParams; + const roles = await executeGetRoomRoles(rid, this.userId); + + return API.v1.success({ + roles, + }); + }, +); + +type RoomEndpoints = ExtractRoutesFromAPI; + +declare module '@rocket.chat/rest-typings' { + // eslint-disable-next-line @typescript-eslint/naming-convention, @typescript-eslint/no-empty-interface + interface Endpoints extends RoomEndpoints {} +} diff --git a/apps/meteor/app/api/server/v1/users.ts b/apps/meteor/app/api/server/v1/users.ts index c84c5a361d35e..5ebd77e64b7d8 100644 --- a/apps/meteor/app/api/server/v1/users.ts +++ b/apps/meteor/app/api/server/v1/users.ts @@ -1,5 +1,5 @@ import { MeteorError, Team, api, Calendar } from '@rocket.chat/core-services'; -import type { IExportOperation, ILoginToken, IPersonalAccessToken, IUser, UserStatus } from '@rocket.chat/core-typings'; +import { type IExportOperation, type ILoginToken, type IPersonalAccessToken, type IUser, type UserStatus } from '@rocket.chat/core-typings'; import { Users, Subscriptions } from '@rocket.chat/models'; import { isUserCreateParamsPOST, @@ -176,7 +176,13 @@ API.v1.addRoute( twoFactorMethod: 'password', }; - await executeSaveUserProfile.call(this as unknown as Meteor.MethodThisType, userData, this.bodyParams.customFields, twoFactorOptions); + await executeSaveUserProfile.call( + this as unknown as Meteor.MethodThisType, + this.user, + userData, + this.bodyParams.customFields, + twoFactorOptions, + ); return API.v1.success({ user: await Users.findOneById(this.userId, { projection: API.v1.defaultFieldsToExclude }), diff --git a/apps/meteor/app/apps/server/bridges/rooms.ts b/apps/meteor/app/apps/server/bridges/rooms.ts index ea8a7fdf6e7e6..861f7ade14dca 100644 --- a/apps/meteor/app/apps/server/bridges/rooms.ts +++ b/apps/meteor/app/apps/server/bridges/rooms.ts @@ -237,11 +237,9 @@ export class AppRoomBridge extends RoomBridge { } private async getUsersByRoomIdAndSubscriptionRole(roomId: string, role: string): Promise { - const subs = (await Subscriptions.findByRoomIdAndRoles(roomId, [role], { + const subs = await Subscriptions.findByRoomIdAndRoles<{ uid: string }>(roomId, [role], { projection: { uid: '$u._id', _id: 0 }, - }).toArray()) as unknown as { - uid: string; - }[]; + }).toArray(); // Was this a bug? const users = await Users.findByIds(subs.map((user: { uid: string }) => user.uid)).toArray(); const userConverter = this.orch.getConverters().get('users'); diff --git a/apps/meteor/app/lib/server/functions/saveUser/validateUserEditing.ts b/apps/meteor/app/lib/server/functions/saveUser/validateUserEditing.ts index 582611399c408..2fd4c1ba9e77c 100644 --- a/apps/meteor/app/lib/server/functions/saveUser/validateUserEditing.ts +++ b/apps/meteor/app/lib/server/functions/saveUser/validateUserEditing.ts @@ -45,7 +45,7 @@ export async function validateUserEditing(userId: IUser['_id'], userData: Update if ( isEditingField(user.username, userData.username) && !settings.get('Accounts_AllowUsernameChange') && - (!canEditOtherUserInfo || editingMyself) + (!canEditOtherUserInfo || (editingMyself && user.username)) ) { throw new MeteorError('error-action-not-allowed', 'Edit username is not allowed', { method: 'insertOrUpdateUser', diff --git a/apps/meteor/app/lib/server/lib/deprecationWarningLogger.ts b/apps/meteor/app/lib/server/lib/deprecationWarningLogger.ts index 5b76b007c1620..5bcf214268a9d 100644 --- a/apps/meteor/app/lib/server/lib/deprecationWarningLogger.ts +++ b/apps/meteor/app/lib/server/lib/deprecationWarningLogger.ts @@ -1,7 +1,7 @@ import { Logger } from '@rocket.chat/logger'; import semver from 'semver'; -import { metrics } from '../../../metrics/server'; +import { metrics } from '../../../metrics/server/lib/metrics'; const deprecationLogger = new Logger('DeprecationWarning'); diff --git a/apps/meteor/app/lib/server/methods/getRoomRoles.ts b/apps/meteor/app/lib/server/methods/getRoomRoles.ts index eb8fea1e602bf..050992445cc79 100644 --- a/apps/meteor/app/lib/server/methods/getRoomRoles.ts +++ b/apps/meteor/app/lib/server/methods/getRoomRoles.ts @@ -1,17 +1,19 @@ -import type { IRoom, ISubscription } from '@rocket.chat/core-typings'; +import type { IRoom } from '@rocket.chat/core-typings'; import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Rooms } from '@rocket.chat/models'; import { check } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; +import type { RoomRoles } from '../../../../server/lib/roles/getRoomRoles'; import { getRoomRoles } from '../../../../server/lib/roles/getRoomRoles'; import { canAccessRoomAsync } from '../../../authorization/server'; import { settings } from '../../../settings/server'; +import { methodDeprecationLogger } from '../lib/deprecationWarningLogger'; declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { - getRoomRoles(rid: IRoom['_id']): ISubscription[]; + getRoomRoles(rid: IRoom['_id']): RoomRoles[]; } } @@ -36,6 +38,7 @@ export const executeGetRoomRoles = async (rid: IRoom['_id'], fromUserId?: string Meteor.methods({ async getRoomRoles(rid) { + methodDeprecationLogger.method('getRoomRoles', '8.0.0', 'Use the /v1/room.getRoles endpoint instead'); const fromUserId = Meteor.userId(); return executeGetRoomRoles(rid, fromUserId); diff --git a/apps/meteor/app/lib/server/methods/getUserRoles.ts b/apps/meteor/app/lib/server/methods/getUserRoles.ts index cbd4712930a28..d9f8939611ac3 100644 --- a/apps/meteor/app/lib/server/methods/getUserRoles.ts +++ b/apps/meteor/app/lib/server/methods/getUserRoles.ts @@ -1,17 +1,25 @@ import { Authorization } from '@rocket.chat/core-services'; -import type { IUser, IRocketChatRecord } from '@rocket.chat/core-typings'; +import type { IUser } from '@rocket.chat/core-typings'; import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Meteor } from 'meteor/meteor'; +import { methodDeprecationLogger } from '../lib/deprecationWarningLogger'; + declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { - getUserRoles(): (IRocketChatRecord & Pick)[]; + getUserRoles(): Pick[]; } } Meteor.methods({ async getUserRoles() { + methodDeprecationLogger.method( + 'getUserRoles', + '8.0.0', + 'This method is deprecated and will be removed in the future. Use the /v1/roles.getUsersInPublicRoles endpoint instead.', + ); + if (!Meteor.userId()) { throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'getUserRoles' }); } diff --git a/apps/meteor/app/lib/server/methods/saveCustomFields.ts b/apps/meteor/app/lib/server/methods/saveCustomFields.ts index d683e59a905cc..be4b4c065fccc 100644 --- a/apps/meteor/app/lib/server/methods/saveCustomFields.ts +++ b/apps/meteor/app/lib/server/methods/saveCustomFields.ts @@ -4,6 +4,7 @@ import { Meteor } from 'meteor/meteor'; import { saveCustomFields } from '../functions/saveCustomFields'; import { RateLimiter } from '../lib'; +import { methodDeprecationLogger } from '../lib/deprecationWarningLogger'; declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention @@ -14,6 +15,7 @@ declare module '@rocket.chat/ddp-client' { Meteor.methods({ async saveCustomFields(fields = {}) { + methodDeprecationLogger.method('saveCustomFields', '8.0.0', 'Use the endpoint /v1/users.updateOwnBasicInfo instead'); const uid = Meteor.userId(); if (!uid) { throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'saveCustomFields' }); diff --git a/apps/meteor/app/lib/server/methods/setUsername.ts b/apps/meteor/app/lib/server/methods/setUsername.ts index 58fac75ed3bd7..cc8411ac9a688 100644 --- a/apps/meteor/app/lib/server/methods/setUsername.ts +++ b/apps/meteor/app/lib/server/methods/setUsername.ts @@ -4,6 +4,7 @@ import { Meteor } from 'meteor/meteor'; import { setUsernameWithValidation } from '../functions/setUsername'; import { RateLimiter } from '../lib'; +import { methodDeprecationLogger } from '../lib/deprecationWarningLogger'; declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention @@ -14,6 +15,7 @@ declare module '@rocket.chat/ddp-client' { Meteor.methods({ async setUsername(username, param = {}) { + methodDeprecationLogger.method('setUsername', '8.0.0', 'Use the endpoint /v1/users.updateOwnBasicInfo instead'); check(username, String); const userId = Meteor.userId(); diff --git a/apps/meteor/app/reactions/server/setReaction.ts b/apps/meteor/app/reactions/server/setReaction.ts index be6e5aed4a545..7ad9a3fc100f7 100644 --- a/apps/meteor/app/reactions/server/setReaction.ts +++ b/apps/meteor/app/reactions/server/setReaction.ts @@ -11,6 +11,7 @@ import { canAccessRoomAsync } from '../../authorization/server'; import { hasPermissionAsync } from '../../authorization/server/functions/hasPermission'; import { emoji } from '../../emoji/server'; import { isTheLastMessage } from '../../lib/server/functions/isTheLastMessage'; +import { methodDeprecationLogger } from '../../lib/server/lib/deprecationWarningLogger'; import { notifyOnMessageChange } from '../../lib/server/lib/notifyListener'; export const removeUserReaction = (message: IMessage, reaction: string, username: string) => { @@ -161,6 +162,7 @@ declare module '@rocket.chat/ddp-client' { Meteor.methods({ async setReaction(reaction, messageId, shouldReact) { + methodDeprecationLogger.method('setReaction', '8.0.0', 'Use the endpoint /v1/chat.react instead'); const uid = Meteor.userId(); if (!uid) { throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'setReaction' }); diff --git a/apps/meteor/client/components/message/toolbar/items/actions/ReactionMessageAction.tsx b/apps/meteor/client/components/message/toolbar/items/actions/ReactionMessageAction.tsx index f3e1d00062f05..f8873d35f55bf 100644 --- a/apps/meteor/client/components/message/toolbar/items/actions/ReactionMessageAction.tsx +++ b/apps/meteor/client/components/message/toolbar/items/actions/ReactionMessageAction.tsx @@ -1,6 +1,6 @@ import { isOmnichannelRoom, type IMessage, type IRoom, type ISubscription } from '@rocket.chat/core-typings'; import { useFeaturePreview } from '@rocket.chat/ui-client'; -import { useUser, useMethod } from '@rocket.chat/ui-contexts'; +import { useUser, useEndpoint } from '@rocket.chat/ui-contexts'; import { useCallback } from 'react'; import { useTranslation } from 'react-i18next'; @@ -20,7 +20,7 @@ type ReactionMessageActionProps = { const ReactionMessageAction = ({ message, room, subscription }: ReactionMessageActionProps) => { const chat = useChat(); const user = useUser(); - const setReaction = useMethod('setReaction'); + const setReaction = useEndpoint('POST', '/v1/chat.react'); const quickReactionsEnabled = useFeaturePreview('quickReactions'); const { quickReactions, addRecentEmoji } = useEmojiPickerData(); const { t } = useTranslation(); @@ -44,7 +44,10 @@ const ReactionMessageAction = ({ message, room, subscription }: ReactionMessageA } const toggleReaction = (emoji: string) => { - setReaction(`:${emoji}:`, message._id); + setReaction({ + emoji: `:${emoji}:`, + messageId: message._id, + }); addRecentEmoji(emoji); }; diff --git a/apps/meteor/client/hooks/useRoomRolesQuery.ts b/apps/meteor/client/hooks/useRoomRolesQuery.ts index ff0980e71ae66..cdc012b1b7832 100644 --- a/apps/meteor/client/hooks/useRoomRolesQuery.ts +++ b/apps/meteor/client/hooks/useRoomRolesQuery.ts @@ -1,5 +1,5 @@ import type { IUser, IRole, IRoom } from '@rocket.chat/core-typings'; -import { useMethod, useStream, useUserId } from '@rocket.chat/ui-contexts'; +import { useEndpoint, useStream, useUserId } from '@rocket.chat/ui-contexts'; import { useQuery, useQueryClient, type UseQueryOptions } from '@tanstack/react-query'; import { useEffect } from 'react'; @@ -100,14 +100,16 @@ export const useRoomRolesQuery = (rid: IRoom['_id'], option }); }, [enabled, queryClient, rid, subscribeToNotifyLogged]); - const getRoomRoles = useMethod('getRoomRoles'); + const getRoomRoles = useEndpoint('GET', '/v1/rooms.roles'); return useQuery({ queryKey: roomsQueryKeys.roles(rid), queryFn: async () => { - const results = await getRoomRoles(rid); + const { roles } = await getRoomRoles({ + rid, + }); - return results.map( + return roles.map( (record): RoomRoles => ({ rid: record.rid, u: record.u, diff --git a/apps/meteor/client/hooks/useUserRolesQuery.ts b/apps/meteor/client/hooks/useUserRolesQuery.ts index 854d4afbc029c..d1798459441ca 100644 --- a/apps/meteor/client/hooks/useUserRolesQuery.ts +++ b/apps/meteor/client/hooks/useUserRolesQuery.ts @@ -1,5 +1,5 @@ import type { IRole, IUser } from '@rocket.chat/core-typings'; -import { useMethod, useStream, useUserId } from '@rocket.chat/ui-contexts'; +import { useStream, useUserId, useEndpoint } from '@rocket.chat/ui-contexts'; import type { UseQueryOptions } from '@tanstack/react-query'; import { useQuery, useQueryClient } from '@tanstack/react-query'; import { useEffect } from 'react'; @@ -71,14 +71,14 @@ export const useUserRolesQuery = (options?: UseUserRolesQue }); }, [enabled, queryClient, subscribeToNotifyLogged, uid]); - const getUserRoles = useMethod('getUserRoles'); + const getUserRoles = useEndpoint('GET', '/v1/roles.getUsersInPublicRoles'); return useQuery({ queryKey: rolesQueryKeys.userRoles(), queryFn: async () => { - const results = await getUserRoles(); + const { users } = await getUserRoles(); - return results.map( + return users.map( (record): UserRoles => ({ uid: record._id, roles: record.roles, diff --git a/apps/meteor/client/views/account/profile/AccountProfileForm.tsx b/apps/meteor/client/views/account/profile/AccountProfileForm.tsx index b28b28c3f1f7b..04e1865ff30f1 100644 --- a/apps/meteor/client/views/account/profile/AccountProfileForm.tsx +++ b/apps/meteor/client/views/account/profile/AccountProfileForm.tsx @@ -20,7 +20,6 @@ import { useTranslation, useEndpoint, useUser, - useMethod, useLayout, } from '@rocket.chat/ui-contexts'; import { useMutation } from '@tanstack/react-query'; @@ -105,16 +104,15 @@ const AccountProfileForm = (props: AllHTMLAttributes): ReactEle } }; - // FIXME: replace to endpoint - const updateOwnBasicInfo = useMethod('saveUserProfile'); + const updateOwnBasicInfo = useEndpoint('POST', '/v1/users.updateOwnBasicInfo'); const updateAvatar = useUpdateAvatar(avatar, user?._id || ''); const handleSave = async ({ email, name, username, statusType, statusText, nickname, bio, customFields }: AccountProfileFormValues) => { try { - await updateOwnBasicInfo( - { - realname: name, + await updateOwnBasicInfo({ + data: { + name, ...(user ? getUserEmailAddress(user) !== email && { email } : {}), username, statusText, @@ -123,7 +121,7 @@ const AccountProfileForm = (props: AllHTMLAttributes): ReactEle bio, }, customFields, - ); + }); await updateAvatar(); dispatchToastMessage({ type: 'success', message: t('Profile_saved_successfully') }); diff --git a/apps/meteor/client/views/account/security/ChangePassword.tsx b/apps/meteor/client/views/account/security/ChangePassword.tsx index 5278007a73191..b8ef68f65c34c 100644 --- a/apps/meteor/client/views/account/security/ChangePassword.tsx +++ b/apps/meteor/client/views/account/security/ChangePassword.tsx @@ -1,6 +1,6 @@ import { Box, Field, FieldError, FieldGroup, FieldHint, FieldLabel, FieldRow, PasswordInput } from '@rocket.chat/fuselage'; import { PasswordVerifier, useValidatePassword } from '@rocket.chat/ui-client'; -import { useMethod, useToastMessageDispatch } from '@rocket.chat/ui-contexts'; +import { useEndpoint, useToastMessageDispatch } from '@rocket.chat/ui-contexts'; import type { AllHTMLAttributes } from 'react'; import { useId } from 'react'; import { Controller, useFormContext } from 'react-hook-form'; @@ -40,12 +40,15 @@ const ChangePassword = (props: AllHTMLAttributes) => { const passwordIsValid = useValidatePassword(password); const { allowPasswordChange } = useAllowPasswordChange(); - // FIXME: replace to endpoint - const updatePassword = useMethod('saveUserProfile'); + const updatePassword = useEndpoint('POST', '/v1/users.updateOwnBasicInfo'); const handleSave = async ({ password }: { password?: string }) => { try { - await updatePassword({ newPassword: password }, {}); + await updatePassword({ + data: { + newPassword: password, + }, + }); dispatchToastMessage({ type: 'success', message: t('Password_changed_successfully') }); reset(); } catch (error) { diff --git a/apps/meteor/client/views/root/MainLayout/RegisterUsername.tsx b/apps/meteor/client/views/root/MainLayout/RegisterUsername.tsx index b5700b1203ba1..e47f17c2a8dda 100644 --- a/apps/meteor/client/views/root/MainLayout/RegisterUsername.tsx +++ b/apps/meteor/client/views/root/MainLayout/RegisterUsername.tsx @@ -10,7 +10,6 @@ import { useUserId, useToastMessageDispatch, useAssetWithDarkModePath, - useMethod, useAccountsCustomFields, } from '@rocket.chat/ui-contexts'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; @@ -38,8 +37,8 @@ const RegisterUsername = () => { throw new Error('Invalid user'); } - const setUsername = useMethod('setUsername'); - const saveCustomFields = useMethod('saveCustomFields'); + const setBasicInfo = useEndpoint('POST', '/v1/users.updateOwnBasicInfo'); + const usernameSuggestion = useEndpoint('GET', '/v1/users.getUsernameSuggestion'); const { data, isLoading } = useQuery({ queryKey: ['suggestion'], @@ -69,7 +68,7 @@ const RegisterUsername = () => { const registerUsernameMutation = useMutation({ mutationFn: async (data: RegisterUsernamePayload) => { const { username, ...customFields } = data; - return Promise.all([setUsername(username), saveCustomFields({ ...customFields })]); + return Promise.all([setBasicInfo({ data: { username }, customFields })]); }, onSuccess: () => { dispatchToastMessage({ type: 'success', message: t('Username_has_been_updated') }); diff --git a/apps/meteor/client/views/setupWizard/providers/SetupWizardProvider.tsx b/apps/meteor/client/views/setupWizard/providers/SetupWizardProvider.tsx index be20ada8f08ab..4b355f87002d6 100644 --- a/apps/meteor/client/views/setupWizard/providers/SetupWizardProvider.tsx +++ b/apps/meteor/client/views/setupWizard/providers/SetupWizardProvider.tsx @@ -49,7 +49,7 @@ const SetupWizardProvider = ({ children }: { children: ReactElement }): ReactEle const setShowSetupWizard = useSettingSetValue('Show_Setup_Wizard'); const registerUser = useMethod('registerUser'); - const defineUsername = useMethod('setUsername'); + const setBasicInfo = useEndpoint('POST', '/v1/users.updateOwnBasicInfo'); const loginWithPassword = useLoginWithPassword(); const setForceLogin = useSessionDispatch('forceLogin'); const createRegistrationIntent = useEndpoint('POST', '/v1/cloud.createRegistrationIntent'); @@ -99,11 +99,11 @@ const SetupWizardProvider = ({ children }: { children: ReactElement }): ReactEle setForceLogin(false); - await defineUsername(username); + await setBasicInfo({ data: { username } }); await dispatchSettings([{ _id: 'Organization_Email', value: email }]); void callbacks.run('usernameSet', {}); }, - [registerUser, setForceLogin, defineUsername, dispatchSettings, loginWithPassword, dispatchToastMessage, t], + [registerUser, setForceLogin, setBasicInfo, dispatchSettings, loginWithPassword, dispatchToastMessage, t], ); const saveAgreementData = useCallback( diff --git a/apps/meteor/ee/server/apps/communication/rest.ts b/apps/meteor/ee/server/apps/communication/rest.ts index 9075a385ca0b9..152a43a93a464 100644 --- a/apps/meteor/ee/server/apps/communication/rest.ts +++ b/apps/meteor/ee/server/apps/communication/rest.ts @@ -520,6 +520,7 @@ export class AppsRestApi { try { const adminsRaw = await Users.findUsersInRoles(['admin'], undefined, { projection: { + _id: 1, username: 1, name: 1, nickname: 1, diff --git a/apps/meteor/server/lib/roles/getRoomRoles.ts b/apps/meteor/server/lib/roles/getRoomRoles.ts index e6986c4439dc1..454404d82604e 100644 --- a/apps/meteor/server/lib/roles/getRoomRoles.ts +++ b/apps/meteor/server/lib/roles/getRoomRoles.ts @@ -1,11 +1,24 @@ -import type { IRoom, ISubscription } from '@rocket.chat/core-typings'; +import type { IRoom } from '@rocket.chat/core-typings'; import { Roles, Subscriptions, Users } from '@rocket.chat/models'; import _ from 'underscore'; import { settings } from '../../../app/settings/server'; -export async function getRoomRoles(rid: IRoom['_id']): Promise { - const options = { +export type RoomRoles = { + rid: IRoom['_id']; + u: { + _id: string; + username: string; + name?: string; + }; + roles: string[]; +}; + +export async function getRoomRoles(rid: IRoom['_id']): Promise { + const useRealName = settings.get('UI_Use_Real_Name') === true; + + const roles = await Roles.find({ scope: 'Subscriptions', description: { $exists: true, $ne: '' } }).toArray(); + const subscriptions = await Subscriptions.findByRoomIdAndRoles(rid, _.pluck(roles, '_id'), { sort: { 'u.username': 1 as const, }, @@ -14,16 +27,12 @@ export async function getRoomRoles(rid: IRoom['_id']): Promise u: 1, roles: 1, }, - }; - - const useRealName = settings.get('UI_Use_Real_Name') === true; - - const roles = await Roles.find({ scope: 'Subscriptions', description: { $exists: true, $ne: '' } }).toArray(); - const subscriptions = await Subscriptions.findByRoomIdAndRoles(rid, _.pluck(roles, '_id'), options).toArray(); + }).toArray(); if (!useRealName) { return subscriptions; } + return Promise.all( subscriptions.map(async (subscription) => { const user = await Users.findOneById(subscription.u._id); diff --git a/apps/meteor/server/methods/saveUserProfile.ts b/apps/meteor/server/methods/saveUserProfile.ts index b3a53a9485ab2..28567dd32a977 100644 --- a/apps/meteor/server/methods/saveUserProfile.ts +++ b/apps/meteor/server/methods/saveUserProfile.ts @@ -1,16 +1,18 @@ import { Apps, AppEvents } from '@rocket.chat/apps'; -import type { UserStatus } from '@rocket.chat/core-typings'; +import type { UserStatus, IUser } from '@rocket.chat/core-typings'; import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Users } from '@rocket.chat/models'; import { Accounts } from 'meteor/accounts-base'; import { Match, check } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; +import type { UpdateFilter } from 'mongodb'; import { twoFactorRequired } from '../../app/2fa/server/twoFactorRequired'; import { getUserInfo } from '../../app/api/server/helpers/getUserInfo'; import { saveCustomFields } from '../../app/lib/server/functions/saveCustomFields'; import { validateUserEditing } from '../../app/lib/server/functions/saveUser'; import { saveUserIdentity } from '../../app/lib/server/functions/saveUserIdentity'; +import { methodDeprecationLogger } from '../../app/lib/server/lib/deprecationWarningLogger'; import { notifyOnUserChange } from '../../app/lib/server/lib/notifyListener'; import { passwordPolicy } from '../../app/lib/server/lib/passwordPolicy'; import { setEmailFunction } from '../../app/lib/server/methods/setEmail'; @@ -38,6 +40,7 @@ async function saveUserProfile( customFields: Record, ..._: unknown[] ) { + const unset: UpdateFilter = {}; if (!rcSettings.get('Accounts_AllowUserProfileChange')) { throw new Meteor.Error('error-not-allowed', 'Not allowed', { method: 'saveUserProfile', @@ -138,6 +141,12 @@ async function saveUserProfile( logout: false, }); + if (user.requirePasswordChange) { + await Users.unsetRequirePasswordChange(user._id); + unset.requirePasswordChange = true; + unset.requirePasswordChangeReason = true; + } + await Users.addPasswordToHistory( this.userId, user.services?.password.bcrypt, @@ -166,7 +175,12 @@ async function saveUserProfile( throw new Error('Unexpected error after saving user profile: user not found'); } - void notifyOnUserChange({ clientAction: 'updated', id: updatedUser._id, diff: await getUserInfo(updatedUser) }); + void notifyOnUserChange({ + clientAction: 'updated', + id: updatedUser._id, + diff: await getUserInfo(updatedUser), + ...(Object.keys(unset).length > 0 && { unset }), + }); await Apps.self?.triggerEvent(AppEvents.IPostUserUpdated, { user: updatedUser, previousUser: user }); @@ -199,6 +213,7 @@ declare module '@rocket.chat/ddp-client' { export function executeSaveUserProfile( this: Meteor.MethodThisType, + user: IUser, settings: { email?: string; username?: string; @@ -215,7 +230,10 @@ export function executeSaveUserProfile( check(settings, Object); check(customFields, Match.Maybe(Object)); - if (settings.email || settings.newPassword) { + if ( + (settings.email && user.emails?.length) || + (settings.newPassword && Object.keys(user.services || {}).length && !user.requirePasswordChange) + ) { return saveUserProfileWithTwoFactor.call(this, settings, customFields, ...args); } @@ -224,6 +242,7 @@ export function executeSaveUserProfile( Meteor.methods({ async saveUserProfile(settings, customFields, ...args) { + methodDeprecationLogger.method('saveUserProfile', '8.0.0', 'Use the endpoint /v1/users.updateOwnBasicInfo instead'); check(settings, Object); check(customFields, Match.Maybe(Object)); diff --git a/apps/meteor/server/methods/setUserPassword.ts b/apps/meteor/server/methods/setUserPassword.ts index 9e37c7ee558b1..5f314c71147b4 100644 --- a/apps/meteor/server/methods/setUserPassword.ts +++ b/apps/meteor/server/methods/setUserPassword.ts @@ -6,6 +6,7 @@ import { Meteor } from 'meteor/meteor'; import type { UpdateResult } from 'mongodb'; import { passwordPolicy } from '../../app/lib/server'; +import { methodDeprecationLogger } from '../../app/lib/server/lib/deprecationWarningLogger'; import { notifyOnUserChange } from '../../app/lib/server/lib/notifyListener'; import { compareUserPassword } from '../lib/compareUserPassword'; @@ -18,6 +19,7 @@ declare module '@rocket.chat/ddp-client' { Meteor.methods({ async setUserPassword(password) { + methodDeprecationLogger.method('setUserPassword', '8.0.0', 'Use the endpoint /v1/users.updateOwnBasicInfo instead'); check(password, String); const userId = Meteor.userId(); diff --git a/apps/meteor/server/services/authorization/service.ts b/apps/meteor/server/services/authorization/service.ts index af9801c1da08e..958dce13e1fa0 100644 --- a/apps/meteor/server/services/authorization/service.ts +++ b/apps/meteor/server/services/authorization/service.ts @@ -1,6 +1,6 @@ import type { IAuthorization, RoomAccessValidator } from '@rocket.chat/core-services'; import { License, ServiceClass } from '@rocket.chat/core-services'; -import type { IUser, IRole, IRoom, ISubscription, IRocketChatRecord } from '@rocket.chat/core-typings'; +import type { IUser, IRole, IRoom, ISubscription } from '@rocket.chat/core-typings'; import { Subscriptions, Rooms, Users, Roles, Permissions } from '@rocket.chat/models'; import mem from 'mem'; @@ -106,7 +106,13 @@ export class Authorization extends ServiceClass implements IAuthorization { AuthorizationUtils.addRolePermissionWhiteList(role, permissions); } - async getUsersFromPublicRoles(): Promise<(IRocketChatRecord & Pick)[]> { + async getUsersFromPublicRoles(): Promise< + { + _id: string; + username: string; + roles: string[]; + }[] + > { const roleIds = await this.getPublicRoles(); return this.getUserFromRoles(roleIds); @@ -114,32 +120,32 @@ export class Authorization extends ServiceClass implements IAuthorization { private getPublicRoles = mem( async (): Promise => { - const roles = await Roles.find>( - { scope: 'Users', description: { $exists: true, $ne: '' } }, - { projection: { _id: 1 } }, - ).toArray(); + const roles = Roles.find>({ scope: 'Users', description: { $exists: true, $ne: '' } }, { projection: { _id: 1 } }); - return roles.map(({ _id }) => _id); + return roles.map(({ _id }) => _id).toArray(); }, { maxAge: 10000 }, ); private getUserFromRoles = mem( async (roleIds: string[]) => { - const users = await Users.findUsersInRoles(roleIds, null, { + const users = Users.findUsersInRoles, '_id' | 'username' | 'roles'>>(roleIds, null, { sort: { username: 1, }, projection: { + _id: 1, username: 1, roles: 1, }, - }).toArray(); - - return users.map((user) => ({ - ...user, - roles: user.roles.filter((roleId: string) => roleIds.includes(roleId)), - })); + }); + + return users + .map((user) => ({ + ...user, + roles: user.roles.filter((roleId: string) => roleIds.includes(roleId)), + })) + .toArray(); }, { maxAge: 10000 }, ); diff --git a/apps/meteor/tests/end-to-end/api/roles.ts b/apps/meteor/tests/end-to-end/api/roles.ts index 393883dfc0bd5..774e6a6433686 100644 --- a/apps/meteor/tests/end-to-end/api/roles.ts +++ b/apps/meteor/tests/end-to-end/api/roles.ts @@ -555,4 +555,20 @@ describe('[Roles]', function () { }); }); }); + + describe('[/roles.getUsersInPublicRoles]', () => { + it('should return public roles', async () => { + await request + .get(api('roles.getUsersInPublicRoles')) + .set(credentials) + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.property('users'); + expect(res.body.users).to.be.an('array'); + // payload schema is checked in the endpoint + expect(res.body.users.length).to.be.greaterThan(0); + }); + }); + }); }); diff --git a/apps/meteor/tests/end-to-end/api/rooms.ts b/apps/meteor/tests/end-to-end/api/rooms.ts index ef3107e2f8034..0e4e0d183d9f3 100644 --- a/apps/meteor/tests/end-to-end/api/rooms.ts +++ b/apps/meteor/tests/end-to-end/api/rooms.ts @@ -4431,4 +4431,27 @@ describe('[Rooms]', () => { }); }); }); + + describe('/rooms.roles', () => { + let testChannel: IRoom; + + before(async () => { + testChannel = (await createRoom({ type: 'c', name: `channel.test.${Date.now()}-${Math.random()}` })).body.channel; + }); + + after(() => deleteRoom({ type: 'c', roomId: testChannel._id })); + + it('should get room roles', async () => { + const response = await request.get(api('rooms.roles')).set(credentials).query({ rid: testChannel._id }).expect(200); + expect(response.body.success).to.be.true; + // the schema is already validated in the server on TEST mode + expect(response.body.roles).to.be.an('array'); + // it should have the user roles + expect(response.body.roles).to.have.lengthOf(1); + expect(response.body.roles[0].rid).to.equal(testChannel._id); + expect(response.body.roles[0].roles).to.be.an('array'); + // it should contain owner role + expect(response.body.roles[0].roles).to.include('owner'); + }); + }); }); diff --git a/packages/core-services/src/types/IAuthorization.ts b/packages/core-services/src/types/IAuthorization.ts index eb58a01dc8d7e..abefce601dd4b 100644 --- a/packages/core-services/src/types/IAuthorization.ts +++ b/packages/core-services/src/types/IAuthorization.ts @@ -1,4 +1,4 @@ -import type { IRocketChatRecord, IRoom, IUser } from '@rocket.chat/core-typings'; +import type { IRoom, IUser } from '@rocket.chat/core-typings'; export type RoomAccessValidator = ( room?: Pick, @@ -13,5 +13,5 @@ export interface IAuthorization { canAccessRoom: RoomAccessValidator; canReadRoom: RoomAccessValidator; canAccessRoomId(rid: IRoom['_id'], uid?: IUser['_id']): Promise; - getUsersFromPublicRoles(): Promise<(IRocketChatRecord & Pick)[]>; + getUsersFromPublicRoles(): Promise, '_id' | 'username' | 'roles'>[]>; } diff --git a/packages/model-typings/src/models/ISubscriptionsModel.ts b/packages/model-typings/src/models/ISubscriptionsModel.ts index 6ca771ef2d84c..150a40e041df2 100644 --- a/packages/model-typings/src/models/ISubscriptionsModel.ts +++ b/packages/model-typings/src/models/ISubscriptionsModel.ts @@ -17,6 +17,7 @@ import type { } from 'mongodb'; import type { IBaseModel } from './IBaseModel'; +import type { DocumentWithProjection } from '../types/DocumentWithProjection'; export interface ISubscriptionsModel extends IBaseModel { getBadgeCount(uid: string): Promise; @@ -216,6 +217,13 @@ export interface ISubscriptionsModel extends IBaseModel { typeException: ISubscription['t'], options?: FindOptions, ): FindCursor; + + findByRoomIdAndRoles

= FindOptions

>( + roomId: string, + roles: string[], + options?: O, + ): FindCursor>; + findByRoomIdAndRoles(roomId: string, roles: string[], options?: FindOptions): FindCursor; findByRoomIdAndUserIds( roomId: ISubscription['rid'], diff --git a/packages/model-typings/src/models/IUsersModel.ts b/packages/model-typings/src/models/IUsersModel.ts index 09b6fd9a2cc95..2d50bfd377cb9 100644 --- a/packages/model-typings/src/models/IUsersModel.ts +++ b/packages/model-typings/src/models/IUsersModel.ts @@ -24,10 +24,15 @@ import type { } from 'mongodb'; import type { FindPaginated, IBaseModel } from './IBaseModel'; +import type { DocumentWithProjection } from '../types/DocumentWithProjection'; export interface IUsersModel extends IBaseModel { addRolesByUserId(uid: IUser['_id'], roles: IRole['_id'][]): Promise; - findUsersInRoles(roles: IRole['_id'][] | IRole['_id'], _scope?: null, options?: FindOptions): FindCursor; + findUsersInRoles = FindOptions>( + roles: IRole['_id'][] | IRole['_id'], + _scope?: null, + options?: O, + ): FindCursor>; findPaginatedUsersInRoles(roles: IRole['_id'][] | IRole['_id'], options?: FindOptions): FindPaginated>; findOneByIdWithEmailAddress(uid: IUser['_id'], options?: FindOptions): Promise; findOneByUsername(username: string, options?: FindOptions): Promise; diff --git a/packages/model-typings/src/types/DocumentWithProjection.ts b/packages/model-typings/src/types/DocumentWithProjection.ts new file mode 100644 index 0000000000000..45610528ac6c4 --- /dev/null +++ b/packages/model-typings/src/types/DocumentWithProjection.ts @@ -0,0 +1,15 @@ +import type { FindOptions } from 'mongodb'; + +type Prettify = { + [K in keyof T]: T[K]; +} & {}; + +export type DocumentWithProjection, O extends FindOptions['projection']> = O extends { + projection: infer P; +} + ? P extends FindOptions['projection'] + ? keyof P extends keyof T + ? Prettify> + : T + : T + : T; diff --git a/packages/models/src/models/Roles.ts b/packages/models/src/models/Roles.ts index cda6984c54560..f636d9044e13a 100644 --- a/packages/models/src/models/Roles.ts +++ b/packages/models/src/models/Roles.ts @@ -191,7 +191,7 @@ export class RolesRaw extends BaseRaw implements IRolesModel { ): Promise>; /** @deprecated function getUsersInRole should be used instead */ - async findUsersInRole

( + async findUsersInRole

( roleId: IRole['_id'], scope: IRoom['_id'] | undefined, options?: any | undefined, @@ -211,7 +211,7 @@ export class RolesRaw extends BaseRaw implements IRolesModel { return Subscriptions.findUsersInRoles([role._id], scope, options); case 'Users': default: - return Users.findUsersInRoles([role._id], null, options); + return Users.findUsersInRoles([role._id], null, options); } } diff --git a/packages/models/src/models/Subscriptions.ts b/packages/models/src/models/Subscriptions.ts index d8b2f1a90b3dd..1829bdcb2d94b 100644 --- a/packages/models/src/models/Subscriptions.ts +++ b/packages/models/src/models/Subscriptions.ts @@ -1147,15 +1147,19 @@ export class SubscriptionsRaw extends BaseRaw implements ISubscri * @param {IRole['_id'][]} roles the list of roles * @param {any} options */ - findByRoomIdAndRoles(roomId: string, roles: string[], options?: FindOptions): FindCursor { - roles = ([] as string[]).concat(roles); + findByRoomIdAndRoles: ISubscriptionsModel['findByRoomIdAndRoles'] = ( + roomId: string, + roles: string[], + options?: FindOptions, + ) => { + const rolesArray = ([] as string[]).concat(roles); const query = { rid: roomId, - roles: { $in: roles }, + roles: { $in: rolesArray }, }; return this.find(query, options); - } + }; countByRoomIdAndRoles(roomId: string, roles: string[]): Promise { roles = ([] as string[]).concat(roles); diff --git a/packages/models/src/models/Users.ts b/packages/models/src/models/Users.ts index 0e82ac6352d56..0481483ccfb54 100644 --- a/packages/models/src/models/Users.ts +++ b/packages/models/src/models/Users.ts @@ -156,7 +156,7 @@ export class UsersRaw extends BaseRaw> implements IU * @param {null} scope the value for the role scope (room id) - not used in the users collection * @param {any} options */ - findUsersInRoles(roles: IRole['_id'][] | IRole['_id'], _scope?: null, options?: FindOptions) { + findUsersInRoles: IUsersModel['findUsersInRoles'] = (roles: IRole['_id'][] | IRole['_id'], _scope?: null, options?: any) => { roles = ([] as string[]).concat(roles); const query = { @@ -164,7 +164,7 @@ export class UsersRaw extends BaseRaw> implements IU }; return this.find(query, options); - } + }; countUsersInRoles(roles: IRole['_id'][] | IRole['_id']) { roles = ([] as string[]).concat(roles); diff --git a/packages/web-ui-registration/src/ResetPassword/ResetPasswordPage.tsx b/packages/web-ui-registration/src/ResetPassword/ResetPasswordPage.tsx index eaba08fc65daf..96ebcfdbfecba 100644 --- a/packages/web-ui-registration/src/ResetPassword/ResetPasswordPage.tsx +++ b/packages/web-ui-registration/src/ResetPassword/ResetPasswordPage.tsx @@ -3,7 +3,16 @@ import { Button, FieldGroup, Field, FieldLabel, ButtonGroup, PasswordInput, Fiel import { Form } from '@rocket.chat/layout'; import { PasswordVerifier, useValidatePassword } from '@rocket.chat/ui-client'; import type { TranslationKey } from '@rocket.chat/ui-contexts'; -import { useSetting, useRouter, useRouteParameter, useUser, useMethod, useTranslation, useLoginWithToken } from '@rocket.chat/ui-contexts'; +import { + useSetting, + useRouter, + useRouteParameter, + useUser, + useMethod, + useTranslation, + useLoginWithToken, + useEndpoint, +} from '@rocket.chat/ui-contexts'; import type { ReactElement } from 'react'; import { useEffect, useId, useRef } from 'react'; import { useForm } from 'react-hook-form'; @@ -18,7 +27,7 @@ const getChangePasswordReason = ({ const ResetPasswordPage = (): ReactElement => { const user = useUser(); const t = useTranslation(); - const setUserPassword = useMethod('setUserPassword'); + const setBasicInfo = useEndpoint('POST', '/v1/users.updateOwnBasicInfo'); const resetPassword = useMethod('resetPassword'); const token = useRouteParameter('token'); @@ -67,7 +76,11 @@ const ResetPasswordPage = (): ReactElement => { await loginWithToken(result.token); router.navigate('/home'); } else { - await setUserPassword(password); + await setBasicInfo({ + data: { + newPassword: password, + }, + }); } } catch ({ error, reason }: any) { const _error = reason ?? error;