chore: migrate 2FA TOTP DDP methods to /v1/users.totp.* REST endpoints#40734
chore: migrate 2FA TOTP DDP methods to /v1/users.totp.* REST endpoints#40734ggazzo wants to merge 2 commits into
Conversation
Added five new REST endpoints under /v1/users.totp.* covering the TOTP
flows that previously only existed as DDP methods:
- POST /v1/users.totp.enable (2fa:enable)
- POST /v1/users.totp.disable (2fa:disable)
- POST /v1/users.totp.validate (2fa:validateTempToken)
- POST /v1/users.totp.regenerateCodes (2fa:regenerateCodes)
- GET /v1/users.totp.codesRemaining (2fa:checkCodesRemaining)
Each DDP method body was extracted into a shared function under
apps/meteor/app/2fa/server/functions/totp.ts; the DDP methods now log
deprecation pointing at the new routes and delegate.
validate keeps the post-enable login-token rotation: the REST endpoint
forwards the request's X-Auth-Token (this.token) so non-PAT tokens get
revoked just like the DDP path did via this.connection.httpHeaders.
Client TwoFactorTOTP swapped from five useMethod hooks to five
useEndpoint hooks. disable response shape changed from bare boolean to
{ disabled: boolean }; verify/regenerate continue to return { codes }.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
Looks like this PR is not ready to merge, because of the following issues:
Please fix the issues and try again If you have any trouble, please check the PR guidelines |
🦋 Changeset detectedLatest commit: 616b525 The changes in this PR will be included in the next version bump. This PR includes changesets to release 3 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (11)
📜 Recent review details⏰ Context from checks skipped due to timeout. (2)
|
| Layer / File(s) | Summary |
|---|---|
REST type contracts packages/rest-typings/src/v1/users.ts |
UsersEndpoints extended with five TOTP routes: enable, disable, validate, regenerateCodes (POST), and codesRemaining (GET) with typed request/response shapes. |
Shared TOTP server functions apps/meteor/app/2fa/server/functions/totp.ts |
New module implementing requireUser, enableTotp, disableTotp, validateTotpTempToken, regenerateTotpCodes, and codesRemainingTotp, consolidating all TOTP business logic previously inlined in DDP methods. |
DDP methods delegating to shared functions apps/meteor/app/2fa/server/methods/enable.ts, disable.ts, validateTempToken.ts, regenerateCodes.ts, checkCodesRemaining.ts |
Each method's inline implementation replaced by a methodDeprecationLogger call and delegation to the corresponding shared TOTP function. |
REST endpoint registration apps/meteor/app/api/server/v1/users.ts |
Five authenticated routes under users.totp.* registered with AJV schemas, delegating handlers to the shared TOTP functions. |
Client migration to REST apps/meteor/client/views/account/security/TwoFactorTOTP.tsx |
useMethod replaced with useEndpoint for all five TOTP operations; call signatures updated to object payloads { code } and response field checks updated (disabled, codes). |
Changesets .changeset/ddp-migrate-batch5-totp-caller.md, .changeset/rest-users-totp.md |
Patch and minor changeset entries documenting the migration and new endpoints. |
Estimated code review effort
🎯 3 (Moderate) | ⏱️ ~25 minutes
Suggested labels
type: chore
Suggested reviewers
- tassoevan
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
| Check name | Status | Explanation |
|---|---|---|
| Description Check | ✅ Passed | Check skipped - CodeRabbit’s high-level summary is enabled. |
| Title check | ✅ Passed | The title clearly summarizes the main change: migrating 2FA TOTP DDP methods to the new REST endpoints. |
| Docstring Coverage | ✅ Passed | No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check. |
| Linked Issues check | ✅ Passed | Check skipped because no linked issues were found for this pull request. |
| Out of Scope Changes check | ✅ Passed | Check skipped because no linked issues were found for this pull request. |
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.
Comment @coderabbitai help to get the list of available commands.
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## develop #40734 +/- ##
===========================================
- Coverage 70.13% 70.01% -0.13%
===========================================
Files 3368 3374 +6
Lines 130022 130179 +157
Branches 22582 22760 +178
===========================================
- Hits 91191 91141 -50
- Misses 35519 35708 +189
- Partials 3312 3330 +18
Flags with carried forward coverage won't be shown. Click here to find out more. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
3 issues found across 11 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="apps/meteor/client/views/account/security/TwoFactorTOTP.tsx">
<violation number="1" location="apps/meteor/client/views/account/security/TwoFactorTOTP.tsx:129">
P2: Invalid regenerate-code attempts now reject through `catch` instead of returning a falsy result, so users get the raw endpoint error instead of `Invalid_two_factor_code`. Handle the REST `invalid-totp` failure explicitly or make the endpoint preserve the old return shape.</violation>
</file>
<file name="apps/meteor/app/api/server/v1/users.ts">
<violation number="1" location="apps/meteor/app/api/server/v1/users.ts:2090">
P2: Include the new TOTP route chain in the exported endpoint type extraction. Otherwise REST consumers do not get typings for these newly added endpoints despite the routes being registered.
(Based on your team's feedback about keeping API typings aligned with runtime endpoints.) [FEEDBACK_USED]</violation>
</file>
<file name="apps/meteor/app/2fa/server/functions/totp.ts">
<violation number="1" location="apps/meteor/app/2fa/server/functions/totp.ts:91">
P1: REST validation double-hashes the current auth token before excluding it from token revocation. Enabling TOTP via `/users.totp.validate` can revoke the caller’s own login token instead of only other non-PAT sessions.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
| await Users.enable2FAAndSetSecretAndCodesByUserId(user._id, user.services.totp.tempSecret, hashedCodes); | ||
|
|
||
| if (authToken) { | ||
| const hashedToken = Accounts._hashLoginToken(authToken); |
There was a problem hiding this comment.
P1: REST validation double-hashes the current auth token before excluding it from token revocation. Enabling TOTP via /users.totp.validate can revoke the caller’s own login token instead of only other non-PAT sessions.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/meteor/app/2fa/server/functions/totp.ts, line 91:
<comment>REST validation double-hashes the current auth token before excluding it from token revocation. Enabling TOTP via `/users.totp.validate` can revoke the caller’s own login token instead of only other non-PAT sessions.</comment>
<file context>
@@ -0,0 +1,154 @@
+ await Users.enable2FAAndSetSecretAndCodesByUserId(user._id, user.services.totp.tempSecret, hashedCodes);
+
+ if (authToken) {
+ const hashedToken = Accounts._hashLoginToken(authToken);
+
+ const { modifiedCount } = await Users.removeNonPATLoginTokensExcept(user._id, hashedToken);
</file context>
| const onRegenerate = async (authCode: string): Promise<void> => { | ||
| try { | ||
| const result = await regenerateCodesFn(authCode); | ||
| const result = await regenerateCodesFn({ code: authCode }); |
There was a problem hiding this comment.
P2: Invalid regenerate-code attempts now reject through catch instead of returning a falsy result, so users get the raw endpoint error instead of Invalid_two_factor_code. Handle the REST invalid-totp failure explicitly or make the endpoint preserve the old return shape.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/meteor/client/views/account/security/TwoFactorTOTP.tsx, line 129:
<comment>Invalid regenerate-code attempts now reject through `catch` instead of returning a falsy result, so users get the raw endpoint error instead of `Invalid_two_factor_code`. Handle the REST `invalid-totp` failure explicitly or make the endpoint preserve the old return shape.</comment>
<file context>
@@ -126,9 +126,9 @@ const TwoFactorTOTP = (props: TwoFactorTOTPProps) => {
const onRegenerate = async (authCode: string): Promise<void> => {
try {
- const result = await regenerateCodesFn(authCode);
+ const result = await regenerateCodesFn({ code: authCode });
- if (!result) {
</file context>
| @@ -52,6 +52,13 @@ import { sendForgotPasswordEmail } from '../../../../server/methods/sendForgotPa | |||
| import { executeSetUserActiveStatus } from '../../../../server/methods/setUserActiveStatus'; | |||
There was a problem hiding this comment.
P2: Include the new TOTP route chain in the exported endpoint type extraction. Otherwise REST consumers do not get typings for these newly added endpoints despite the routes being registered.
(Based on your team's feedback about keeping API typings aligned with runtime endpoints.)
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/meteor/app/api/server/v1/users.ts, line 2090:
<comment>Include the new TOTP route chain in the exported endpoint type extraction. Otherwise REST consumers do not get typings for these newly added endpoints despite the routes being registered.
(Based on your team's feedback about keeping API typings aligned with runtime endpoints.) </comment>
<file context>
@@ -2080,6 +2087,143 @@ API.v1
},
);
+API.v1
+ .post(
+ 'users.totp.enable',
</file context>
| async function action() { | ||
| return API.v1.success(await codesRemainingTotp(this.userId)); | ||
| }, |
There was a problem hiding this comment.
Missing Rate Limiting on TOTP REST Endpoints Allows 2FA Bypass via Brute-Force
The REST API endpoints for managing TOTP 2FA (users.totp.disable, users.totp.validate, and users.totp.regenerateCodes) do not define rateLimiterOptions. While the APIClass does enforce a default rate limit for endpoints without explicit rateLimiterOptions (if the global rate limiter is enabled), this default limit (API_Enable_Rate_Limiter_Limit_Calls_Default and API_Enable_Rate_Limiter_Limit_Time_Default) is often too generous for sensitive authentication operations like TOTP code verification.
More critically, the underlying TOTPCheck implementation (./Rocket.Chat/apps/meteor/app/2fa/server/code/TOTPCheck.ts) explicitly returns false for maxFaildedAttemtpsReached, meaning there is no stateful lockout or brute-force protection at the sink level for TOTP codes (unlike email codes, which increment an invalid attempt counter).
Because the default API rate limit is not tuned for brute-force protection of a 6-digit space and there is no sink-level lockout, an attacker who has compromised a user's session can rapidly submit TOTP codes to disable 2FA or regenerate backup codes. If the default rate limit is disabled or bypassed (e.g., via the api-bypass-rate-limit permission or TEST_MODE), the endpoints are completely unprotected. Once 2FA is disabled, the attacker can perform critical actions like changing the password or email, leading to full account takeover.
Steps to Reproduce
import requests
import concurrent.futures
url = "https://your-rocket-chat-instance.com/api/v1/users.totp.disable"
headers = {
"X-Auth-Token": "compromised_session_token",
"X-User-Id": "compromised_user_id",
"Content-Type": "application/json"
}
def try_code(code):
payload = {"code": f"{code:06d}"}
response = requests.post(url, json=payload, headers=headers)
if response.status_code == 200 and response.json().get("disabled"):
print(f"[+] Success! 2FA disabled with code: {code:06d}")
return True
return False
# Brute-force the 6-digit space
with concurrent.futures.ThreadPoolExecutor(max_workers=50) as executor:
executor.map(try_code, range(1000000))Trace
graph TD
subgraph SG0 ["apps/meteor/app/2fa/server/code/EmailCheck.ts"]
EmailCheck.getUserVerifiedEmails["Filters user emails to return only those that are verified."]
EmailCheck.send2FAEmail["Sends a 2FA code via email to the user."]
EmailCheck.sendEmailCode["Generates and sends an email 2FA code to the user."]
end
style SG0 fill:#2a2a2a,stroke:#444,color:#aaa
subgraph SG1 ["apps/meteor/app/2fa/server/code/index.ts"]
getUserForCheck["Retrieves user data required for 2FA checks."]
end
style SG1 fill:#2a2a2a,stroke:#444,color:#aaa
subgraph SG2 ["apps/meteor/app/2fa/server/functions/resetTOTP.ts"]
sendResetNotification_2["sendResetNotification"]
resetTOTP["resetTOTP"]
end
style SG2 fill:#2a2a2a,stroke:#444,color:#aaa
subgraph SG3 ["apps/meteor/app/2fa/server/functions/totp.ts"]
requireUser["Helper to ensure a user exists and is authorized."]
enableTotp["Enables TOTP for a user by generating a new secret."]
disableTotp["Disables TOTP for a user after verifying the provided code."]
validateTotpTempToken["Validates a temporary TOTP token and enables TOTP for the user."]
regenerateTotpCodes["Regenerates TOTP backup codes for a user."]
codesRemainingTotp["Returns the number of remaining TOTP backup codes for a user."]
end
style SG3 fill:#2a2a2a,stroke:#444,color:#aaa
subgraph SG4 ["apps/meteor/app/2fa/server/lib/totp.ts"]
TOTP.generateSecret["Generates a new TOTP secret."]
TOTP.generateOtpauthURL["Generates the OTPauth URL for a user."]
TOTP.verify["Verifies a TOTP token or backup code."]
TOTP.generateCodes["Generates 12 backup codes for TOTP."]
end
style SG4 fill:#2a2a2a,stroke:#444,color:#aaa
subgraph SG5 ["apps/meteor/app/api/server/ApiClass.ts"]
APIClass.success["Helper method to format successful API responses."]
APIClass.failure["Helper method to format failed API responses with error details."]
APIClass.forbidden["Helper method for returning 403 Forbidden responses."]
APIClass.this.parseJsonQuery["Parses JSON query parameters for API requests."]
end
style SG5 fill:#2a2a2a,stroke:#444,color:#aaa
subgraph SG6 ["apps/meteor/app/api/server/helpers/getPaginationItems.ts"]
getPaginationItems["Validates and sanitizes pagination query parameters."]
end
style SG6 fill:#2a2a2a,stroke:#444,color:#aaa
subgraph SG7 ["apps/meteor/app/api/server/helpers/getUserFromParams.ts"]
getUserFromParams["Resolves a user object from request parameters."]
end
style SG7 fill:#2a2a2a,stroke:#444,color:#aaa
subgraph SG8 ["apps/meteor/app/api/server/helpers/getUserInfo.ts"]
isVerifiedEmail["Checks if a user has a verified email."]
getUserPreferences["Fetches user-specific preferences from settings."]
filterOutdatedVersionUpdateBanners["Filters banners based on outdated version updates."]
getUserCalendar["Fetches user calendar settings based on email domain."]
getUserInfo["Formats user information for API response."]
end
style SG8 fill:#2a2a2a,stroke:#444,color:#aaa
subgraph SG9 ["apps/meteor/app/api/server/helpers/isUserFromParams.ts"]
isUserFromParams["isUserFromParams"]
end
style SG9 fill:#2a2a2a,stroke:#444,color:#aaa
subgraph SG10 ["apps/meteor/app/api/server/lib/eraseTeam.ts"]
eraseRoomLooseValidation["eraseRoomLooseValidation"]
end
style SG10 fill:#2a2a2a,stroke:#444,color:#aaa
subgraph SG11 ["apps/meteor/app/api/server/lib/getUploadFormData.ts"]
getUploadFormData["getUploadFormData"]
end
style SG11 fill:#2a2a2a,stroke:#444,color:#aaa
subgraph SG12 ["apps/meteor/app/api/server/lib/isValidQuery.ts"]
._Rocket.Chat_apps_meteor_app_api_server_lib_isValidQuery.ts["Validates API queries against allowed attributes and operations."]
end
style SG12 fill:#2a2a2a,stroke:#444,color:#aaa
subgraph SG13 ["apps/meteor/app/api/server/lib/users.ts"]
findUsersToAutocomplete["findUsersToAutocomplete"]
getInclusiveFields["Extracts inclusive fields from a query object."]
getNonEmptyFields["Returns default fields if input fields are empty."]
getNonEmptyQuery["Returns default query if input query is empty."]
findPaginatedUsersByStatus["findPaginatedUsersByStatus"]
end
style SG13 fill:#2a2a2a,stroke:#444,color:#aaa
subgraph SG14 ["apps/meteor/app/api/server/v1/users.ts"]
get["Action handler for listing users."]
action{{"Action handler for user-related operations."}}
end
style SG14 fill:#2a2a2a,stroke:#444,color:#aaa
subgraph SG15 ["apps/meteor/app/authentication/server/startup/index.js"]
Accounts.insertUserDoc["Accounts.insertUserDoc"]
end
style SG15 fill:#2a2a2a,stroke:#444,color:#aaa
subgraph SG16 ["apps/meteor/app/authorization/server/functions/hasPermission.ts"]
hasPermissionAsync["Checks if a user has a specific permission."]
end
style SG16 fill:#2a2a2a,stroke:#444,color:#aaa
subgraph SG17 ["apps/meteor/app/authorization/server/index.ts"]
._Rocket.Chat_apps_meteor_app_authorization_server_index.ts["./Rocket.Chat/apps/meteor/app/authorization/server/index.ts"]
end
style SG17 fill:#2a2a2a,stroke:#444,color:#aaa
subgraph SG18 ["apps/meteor/app/file-upload/server/lib/FileUpload.ts"]
FileUpload.getStore["FileUpload.getStore"]
FileUpload.getStoreByName["FileUpload.getStoreByName"]
FileUpload.get["FileUpload.get"]
FileUpload.removeFilesByRoomId["FileUpload.removeFilesByRoomId"]
end
style SG18 fill:#2a2a2a,stroke:#444,color:#aaa
subgraph SG19 ["apps/meteor/app/file/server/file.server.ts"]
RocketChatFile.dataURIParse["RocketChatFile.dataURIParse"]
end
style SG19 fill:#2a2a2a,stroke:#444,color:#aaa
subgraph SG20 ["apps/meteor/app/invites/server/functions/validateInviteToken.ts"]
validateInviteToken["validateInviteToken"]
end
style SG20 fill:#2a2a2a,stroke:#444,color:#aaa
subgraph SG21 ["apps/meteor/app/lib/server/functions/addUserToDefaultChannels.ts"]
addUserToDefaultChannels["addUserToDefaultChannels"]
end
style SG21 fill:#2a2a2a,stroke:#444,color:#aaa
subgraph SG22 ["apps/meteor/app/lib/server/functions/addUserToRoom.ts"]
addUserToRoom["addUserToRoom"]
end
style SG22 fill:#2a2a2a,stroke:#444,color:#aaa
subgraph SG23 ["apps/meteor/app/lib/server/functions/checkEmailAvailability.ts"]
checkEmailAvailability["checkEmailAvailability"]
end
style SG23 fill:#2a2a2a,stroke:#444,color:#aaa
subgraph SG24 ["apps/meteor/app/lib/server/functions/checkUsernameAvailability.ts"]
toRegExp["toRegExp"]
usernameIsBlocked["usernameIsBlocked"]
checkUsernameAvailabilityWithValidation["checkUsernameAvailabilityWithValidation"]
checkUsernameAvailability["checkUsernameAvailability"]
end
style SG24 fill:#2a2a2a,stroke:#444,color:#aaa
subgraph SG25 ["apps/meteor/app/lib/server/functions/deleteUser.ts"]
deleteUser["deleteUser"]
end
style SG25 fill:#2a2a2a,stroke:#444,color:#aaa
subgraph SG26 ["apps/meteor/app/lib/server/functions/getAvatarSuggestionForUser.ts"]
getAvatarSuggestionForUser["getAvatarSuggestionForUser"]
end
style SG26 fill:#2a2a2a,stroke:#444,color:#aaa
subgraph SG27 ["apps/meteor/app/lib/server/functions/getDefaultChannels.ts"]
getDefaultChannels["getDefaultChannels"]
end
style SG27 fill:#2a2a2a,stroke:#444,color:#aaa
subgraph SG28 ["apps/meteor/app/lib/server/functions/getFullUserData.ts"]
getCustomFields["getCustomFields"]
getFields["getFields"]
findTargetUser["findTargetUser"]
getFullUserDataByUniqueSearchTerm["getFullUserDataByUniqueSearchTerm"]
end
style SG28 fill:#2a2a2a,stroke:#444,color:#aaa
subgraph SG29 ["apps/meteor/app/lib/server/functions/getRoomsWithSingleOwner.ts"]
shouldRemoveOrChangeOwner["shouldRemoveOrChangeOwner"]
getSubscribedRoomsForUserWithDetails["getSubscribedRoomsForUserWithDetails"]
end
style SG29 fill:#2a2a2a,stroke:#444,color:#aaa
subgraph SG30 ["apps/meteor/app/lib/server/functions/getUserSingleOwnedRooms.ts"]
getUserSingleOwnedRooms["getUserSingleOwnedRooms"]
end
style SG30 fill:#2a2a2a,stroke:#444,color:#aaa
subgraph SG31 ["apps/meteor/app/lib/server/functions/getUsernameSuggestion.ts"]
slug["slug"]
usernameIsAvailable["usernameIsAvailable"]
name["name"]
generateUsernameSuggestion["generateUsernameSuggestion"]
end
style SG31 fill:#2a2a2a,stroke:#444,color:#aaa
subgraph SG32 ["apps/meteor/app/lib/server/functions/joinDefaultChannels.ts"]
joinDefaultChannels["joinDefaultChannels"]
end
style SG32 fill:#2a2a2a,stroke:#444,color:#aaa
subgraph SG33 ["apps/meteor/app/lib/server/functions/relinquishRoomOwnerships.ts"]
bulkTeamCleanup["bulkTeamCleanup"]
bulkRoomCleanUp["bulkRoomCleanUp"]
relinquishRoomOwnerships["relinquishRoomOwnerships"]
end
style SG33 fill:#2a2a2a,stroke:#444,color:#aaa
subgraph SG34 ["apps/meteor/app/lib/server/functions/saveCustomFields.ts"]
saveCustomFields["saveCustomFields"]
end
style SG34 fill:#2a2a2a,stroke:#444,color:#aaa
subgraph SG35 ["apps/meteor/app/lib/server/functions/saveCustomFieldsWithoutValidation.ts"]
getCustomFieldsMeta["getCustomFieldsMeta"]
saveCustomFieldsWithoutValidation["saveCustomFieldsWithoutValidation"]
end
style SG35 fill:#2a2a2a,stroke:#444,color:#aaa
subgraph SG36 ["apps/meteor/app/lib/server/functions/saveUser/index.ts"]
._Rocket.Chat_apps_meteor_app_lib_server_functions_saveUser_index.ts["./Rocket.Chat/apps/meteor/app/lib/server/functions/saveUser/index.ts"]
end
style SG36 fill:#2a2a2a,stroke:#444,color:#aaa
subgraph SG37 ["apps/meteor/app/lib/server/functions/saveUser/sendUserEmail.ts"]
sendUserEmail["sendUserEmail"]
sendWelcomeEmail["sendWelcomeEmail"]
end
style SG37 fill:#2a2a2a,stroke:#444,color:#aaa
subgraph SG38 ["apps/meteor/app/lib/server/functions/saveUser/validateUserEditing.ts"]
canEditExtension["canEditExtension"]
end
style SG38 fill:#2a2a2a,stroke:#444,color:#aaa
subgraph SG39 ["apps/meteor/app/lib/server/functions/saveUserIdentity.ts"]
saveUserIdentity["saveUserIdentity"]
updateUsernameReferences["updateUsernameReferences"]
end
style SG39 fill:#2a2a2a,stroke:#444,color:#aaa
subgraph SG40 ["apps/meteor/app/lib/server/functions/setRealName.ts"]
setRealName["setRealName"]
end
style SG40 fill:#2a2a2a,stroke:#444,color:#aaa
subgraph SG41 ["apps/meteor/app/lib/server/functions/setUserAvatar.ts"]
setAvatarFromServiceWithValidation["setAvatarFromServiceWithValidation"]
setUserAvatar["setUserAvatar"]
end
style SG41 fill:#2a2a2a,stroke:#444,color:#aaa
subgraph SG42 ["apps/meteor/app/lib/server/functions/setUsername.ts"]
isUserInFederatedRooms["isUserInFederatedRooms"]
setUsernameWithValidation["setUsernameWithValidation"]
setUsername["_setUsername"]
end
style SG42 fill:#2a2a2a,stroke:#444,color:#aaa
subgraph SG43 ["apps/meteor/app/lib/server/functions/updateGroupDMsName.ts"]
getFname["getFname"]
getName["getName"]
getUsersWhoAreInTheSameGroupDMsAs["getUsersWhoAreInTheSameGroupDMsAs"]
updateGroupDMsName["updateGroupDMsName"]
getMembers["getMembers"]
end
style SG43 fill:#2a2a2a,stroke:#444,color:#aaa
subgraph SG44 ["apps/meteor/app/lib/server/functions/validateCustomFields.js"]
validateCustomFields["validateCustomFields"]
end
style SG44 fill:#2a2a2a,stroke:#444,color:#aaa
subgraph SG45 ["apps/meteor/app/lib/server/functions/validateName.ts"]
validateName["validateName"]
end
style SG45 fill:#2a2a2a,stroke:#444,color:#aaa
subgraph SG46 ["apps/meteor/app/lib/server/functions/validateNameChars.ts"]
validateNameChars["validateNameChars"]
end
style SG46 fill:#2a2a2a,stroke:#444,color:#aaa
subgraph SG47 ["apps/meteor/app/lib/server/functions/validateUsername.ts"]
validateUsername["validateUsername"]
end
style SG47 fill:#2a2a2a,stroke:#444,color:#aaa
subgraph SG48 ["apps/meteor/app/lib/server/index.ts"]
._Rocket.Chat_apps_meteor_app_lib_server_index.ts["./Rocket.Chat/apps/meteor/app/lib/server/index.ts"]
end
style SG48 fill:#2a2a2a,stroke:#444,color:#aaa
subgraph SG49 ["apps/meteor/app/lib/server/lib/deprecationWarningLogger.ts"]
compareVersions["Compares versions and potentially throws errors for deprecated usage."]
method["Logs and writes headers for deprecated method usage."]
warn["Logs a deprecation warning."]
end
style SG49 fill:#2a2a2a,stroke:#444,color:#aaa
subgraph SG50 ["apps/meteor/app/lib/server/lib/notifyListener.ts"]
notifyOnRoomChangedById["notifyOnRoomChangedById"]
notifyOnRoomChangedByUsernamesOrUids["notifyOnRoomChangedByUsernamesOrUids"]
notifyOnIntegrationChangedByUserId["notifyOnIntegrationChangedByUserId"]
notifyOnLivechatDepartmentAgentChanged["notifyOnLivechatDepartmentAgentChanged"]
notifyOnSettingChangedById["notifyOnSettingChangedById"]
notifyOnUserChange["Broadcasts a notification about user changes."]
notifyOnUserChangeAsync["Async wrapper for user change notifications."]
notifyOnSubscriptionChanged["notifyOnSubscriptionChanged"]
notifyOnSubscriptionChangedByRoomIdAndUserId["notifyOnSubscriptionChangedByRoomIdAndUserId"]
notifyOnSubscriptionChangedById["notifyOnSubscriptionChangedById"]
notifyOnSubscriptionChangedByUserPreferences["notifyOnSubscriptionChangedByUserPreferences"]
notifyOnSubscriptionChangedByRoomId["notifyOnSubscriptionChangedByRoomId"]
notifyOnSubscriptionChangedByAutoTranslateAndUserId["notifyOnSubscriptionChangedByAutoTranslateAndUserId"]
notifyOnSubscriptionChangedByUserIdAndRoomType["notifyOnSubscriptionChangedByUserIdAndRoomType"]
notifyOnSubscriptionChangedByNameAndRoomType["notifyOnSubscriptionChangedByNameAndRoomType"]
notifyOnSubscriptionChangedByUserId["notifyOnSubscriptionChangedByUserId"]
end
style SG50 fill:#2a2a2a,stroke:#444,color:#aaa
subgraph SG51 ["apps/meteor/app/lib/server/methods/createToken.ts"]
generateAccessToken["generateAccessToken"]
end
style SG51 fill:#2a2a2a,stroke:#444,color:#aaa
subgraph SG52 ["apps/meteor/app/lib/server/methods/deleteUserOwnAccount.ts"]
deleteUserOwnAccount["deleteUserOwnAccount"]
end
style SG52 fill:#2a2a2a,stroke:#444,color:#aaa
subgraph SG53 ["apps/meteor/app/mailer/server/api.ts"]
replace["Replaces variables in a string with provided data."]
wrap["Wraps HTML content with email headers and footers."]
sendNoWrap["Sends an email without wrapping."]
send["Sends a formatted email."]
end
style SG53 fill:#2a2a2a,stroke:#444,color:#aaa
subgraph SG54 ["apps/meteor/app/settings/server/CachedSettings.ts"]
CachedSettings.has["Checks if a setting exists in the cached store."]
CachedSettings.getSetting["CachedSettings.getSetting"]
CachedSettings.get["Retrieves the value of a setting from the cache."]
CachedSettings.getByRegexp["Retrieves multiple setting values matching a regular expression."]
CachedSettings.set["Updates a setting in the cache and emits a change event."]
end
style SG54 fill:#2a2a2a,stroke:#444,color:#aaa
subgraph SG55 ["apps/meteor/app/settings/server/SettingsRegistry.ts"]
SettingsRegistry.add["SettingsRegistry.add"]
SettingsRegistry.saveUpdatedSetting["SettingsRegistry.saveUpdatedSetting"]
end
style SG55 fill:#2a2a2a,stroke:#444,color:#aaa
subgraph SG56 ["apps/meteor/app/settings/server/functions/getSettingDefaults.ts"]
getSettingDefaults["getSettingDefaults"]
end
style SG56 fill:#2a2a2a,stroke:#444,color:#aaa
subgraph SG57 ["apps/meteor/app/settings/server/functions/validateSetting.ts"]
validateSetting["validateSetting"]
end
style SG57 fill:#2a2a2a,stroke:#444,color:#aaa
subgraph SG58 ["apps/meteor/app/utils/lib/getDefaultSubscriptionPref.ts"]
getDefaultSubscriptionPref["getDefaultSubscriptionPref"]
end
style SG58 fill:#2a2a2a,stroke:#444,color:#aaa
subgraph SG59 ["apps/meteor/app/utils/lib/getURL.ts"]
getURLWithoutSettings["Wrapper for _getURL that accepts explicit configuration instead of reading from settings."]
end
style SG59 fill:#2a2a2a,stroke:#444,color:#aaa
subgraph SG60 ["apps/meteor/app/utils/lib/mimeTypes.ts"]
getMimeTypeFromFileName["getMimeTypeFromFileName"]
getMimeType["getMimeType"]
end
style SG60 fill:#2a2a2a,stroke:#444,color:#aaa
subgraph SG61 ["apps/meteor/app/utils/server/functions/isSMTPConfigured.ts"]
isSMTPConfigured["isSMTPConfigured"]
end
style SG61 fill:#2a2a2a,stroke:#444,color:#aaa
subgraph SG62 ["apps/meteor/app/utils/server/getURL.ts"]
getURL["Server-side utility to resolve a URL path using current application settings."]
end
style SG62 fill:#2a2a2a,stroke:#444,color:#aaa
subgraph SG63 ["apps/meteor/app/utils/server/lib/getUserPreference.ts"]
getUserPreference["Retrieves a specific user preference, falling back to system defaults if not set."]
end
style SG63 fill:#2a2a2a,stroke:#444,color:#aaa
subgraph SG64 ["apps/meteor/client/meteor/overrides/userAndUsers.ts"]
Meteor.userId["Override for Meteor.userId to bridge with internal user state storage."]
end
style SG64 fill:#2a2a2a,stroke:#444,color:#aaa
subgraph SG65 ["apps/meteor/client/meteor/user.ts"]
watchUserId["Watches the current user ID store for changes using Meteor reactivity."]
end
style SG65 fill:#2a2a2a,stroke:#444,color:#aaa
subgraph SG66 ["apps/meteor/client/meteor/watch.ts"]
watch["Connects Meteor Tracker reactivity to Zustand store updates."]
end
style SG66 fill:#2a2a2a,stroke:#444,color:#aaa
subgraph SG67 ["apps/meteor/imports/personal-access-tokens/server/api/methods/generateToken.ts"]
generatePersonalAccessTokenOfUser["generatePersonalAccessTokenOfUser"]
end
style SG67 fill:#2a2a2a,stroke:#444,color:#aaa
subgraph SG68 ["apps/meteor/imports/personal-access-tokens/server/api/methods/regenerateToken.ts"]
regeneratePersonalAccessTokenOfUser["regeneratePersonalAccessTokenOfUser"]
end
style SG68 fill:#2a2a2a,stroke:#444,color:#aaa
subgraph SG69 ["apps/meteor/imports/personal-access-tokens/server/api/methods/removeToken.ts"]
removePersonalAccessTokenOfUser["removePersonalAccessTokenOfUser"]
end
style SG69 fill:#2a2a2a,stroke:#444,color:#aaa
subgraph SG70 ["apps/meteor/lib/utils/isObject.ts"]
isObject["Checks if a value is a non-null object or function."]
end
style SG70 fill:#2a2a2a,stroke:#444,color:#aaa
subgraph SG71 ["apps/meteor/lib/utils/parseCSV.ts"]
parseCSV["parseCSV"]
end
style SG71 fill:#2a2a2a,stroke:#444,color:#aaa
subgraph SG72 ["apps/meteor/lib/utils/stringUtils.ts"]
makeString["Converts input to a string, returning empty string for falsy inputs."]
defaultToWhiteSpace["Converts input characters to a regex-safe whitespace string."]
trim["Trims whitespace or specified characters from both ends of a string."]
end
style SG72 fill:#2a2a2a,stroke:#444,color:#aaa
subgraph SG73 ["apps/meteor/server/database/utils.ts"]
isExtendedSession["isExtendedSession"]
onceTransactionCommitedSuccessfully["onceTransactionCommitedSuccessfully"]
withError["withError"]
end
style SG73 fill:#2a2a2a,stroke:#444,color:#aaa
subgraph SG74 ["apps/meteor/server/lib/auditServerEvents/userChanged.ts"]
UserChangedAuditStore.constructor["UserChangedAuditStore.constructor"]
end
style SG74 fill:#2a2a2a,stroke:#444,color:#aaa
subgraph SG75 ["apps/meteor/server/lib/dataExport/getPath.ts"]
getPath["getPath"]
end
style SG75 fill:#2a2a2a,stroke:#444,color:#aaa
subgraph SG76 ["apps/meteor/server/lib/getSubscriptionAutotranslateDefaultConfig.ts"]
getSubscriptionAutotranslateDefaultConfig["getSubscriptionAutotranslateDefaultConfig"]
end
style SG76 fill:#2a2a2a,stroke:#444,color:#aaa
subgraph SG77 ["apps/meteor/server/lib/isUserIdFederated.ts"]
isUserIdFederated["isUserIdFederated"]
end
style SG77 fill:#2a2a2a,stroke:#444,color:#aaa
subgraph SG78 ["apps/meteor/server/lib/resetUserE2EKey.ts"]
sendResetNotification["sendResetNotification"]
resetUserE2EEncriptionKey["resetUserE2EEncriptionKey"]
end
style SG78 fill:#2a2a2a,stroke:#444,color:#aaa
subgraph SG79 ["apps/meteor/server/lib/roles/addUserRoles.ts"]
addUserRolesAsync["addUserRolesAsync"]
end
style SG79 fill:#2a2a2a,stroke:#444,color:#aaa
subgraph SG80 ["apps/meteor/server/lib/roles/syncRoomRolePriority.ts"]
syncRoomRolePriorityForUserAndRoom["syncRoomRolePriorityForUserAndRoom"]
end
style SG80 fill:#2a2a2a,stroke:#444,color:#aaa
subgraph SG81 ["apps/meteor/server/lib/roles/validateRoleList.ts"]
validateRoleList["validateRoleList"]
end
style SG81 fill:#2a2a2a,stroke:#444,color:#aaa
subgraph SG82 ["apps/meteor/server/methods/registerUser.ts"]
registerUser["registerUser"]
end
style SG82 fill:#2a2a2a,stroke:#444,color:#aaa
subgraph SG83 ["apps/meteor/server/methods/requestDataDownload.ts"]
requestDataDownload["requestDataDownload"]
end
style SG83 fill:#2a2a2a,stroke:#444,color:#aaa
subgraph SG84 ["apps/meteor/server/methods/resetAvatar.ts"]
resetAvatar["resetAvatar"]
userId["userId"]
end
style SG84 fill:#2a2a2a,stroke:#444,color:#aaa
subgraph SG85 ["apps/meteor/server/methods/saveUserPreferences.ts"]
updateNotificationPreferences["updateNotificationPreferences"]
saveUserPreferences["saveUserPreferences"]
end
style SG85 fill:#2a2a2a,stroke:#444,color:#aaa
subgraph SG86 ["apps/meteor/server/methods/sendConfirmationEmail.ts"]
sendConfirmationEmail["sendConfirmationEmail"]
end
style SG86 fill:#2a2a2a,stroke:#444,color:#aaa
subgraph SG87 ["apps/meteor/server/methods/sendForgotPasswordEmail.ts"]
sendForgotPasswordEmail["sendForgotPasswordEmail"]
end
style SG87 fill:#2a2a2a,stroke:#444,color:#aaa
subgraph SG88 ["apps/meteor/server/methods/setUserActiveStatus.ts"]
executeSetUserActiveStatus["executeSetUserActiveStatus"]
setUserActiveStatus["setUserActiveStatus"]
end
style SG88 fill:#2a2a2a,stroke:#444,color:#aaa
subgraph SG89 ["apps/meteor/server/services/authorization/service.ts"]
Authorization.hasPermission["Checks if a user has a specific permission."]
Authorization.all["Internal helper to verify if a user has all provided permissions."]
end
style SG89 fill:#2a2a2a,stroke:#444,color:#aaa
subgraph SG90 ["apps/meteor/server/services/user/lib/getNewUserRoles.ts"]
getNewUserRoles["getNewUserRoles"]
end
style SG90 fill:#2a2a2a,stroke:#444,color:#aaa
subgraph SG91 ["ee/apps/account-service/src/lib/utils.ts"]
generateStampedLoginToken["_generateStampedLoginToken"]
hashLoginToken["Hashes a login token using SHA256 and base64 encoding."]
end
style SG91 fill:#2a2a2a,stroke:#444,color:#aaa
subgraph SG92 ["ee/packages/presence/src/Presence.ts"]
Presence.setupNextExpiration["Presence.setupNextExpiration"]
Presence.updatePresenceAndReschedule["Presence.updatePresenceAndReschedule"]
Presence.setStatus["Presence.setStatus"]
Presence.setActiveState["Presence.setActiveState"]
Presence.clearActiveState["Presence.clearActiveState"]
Presence.updateUserPresence["Presence.updateUserPresence"]
end
style SG92 fill:#2a2a2a,stroke:#444,color:#aaa
subgraph SG93 ["ee/packages/presence/src/lib/normalizeStatusText.ts"]
normalizeStatusText["normalizeStatusText"]
end
style SG93 fill:#2a2a2a,stroke:#444,color:#aaa
subgraph SG94 ["packages/core-services/src/MeteorError.ts"]
MeteorError.constructor["MeteorError.constructor"]
end
style SG94 fill:#2a2a2a,stroke:#444,color:#aaa
subgraph SG95 ["packages/models/src/models/BaseRaw.ts"]
BaseRaw.doNotMixInclusionAndExclusionFields["BaseRaw.doNotMixInclusionAndExclusionFields"]
BaseRaw.find["BaseRaw.find"]
BaseRaw.updateOne["BaseRaw.updateOne"]
BaseRaw.deleteMany["BaseRaw.deleteMany"]
end
style SG95 fill:#2a2a2a,stroke:#444,color:#aaa
subgraph SG96 ["packages/models/src/models/LivechatDepartmentAgents.ts"]
LivechatDepartmentAgentsRaw.findByAgentId["LivechatDepartmentAgentsRaw.findByAgentId"]
LivechatDepartmentAgentsRaw.removeByAgentId["LivechatDepartmentAgentsRaw.removeByAgentId"]
LivechatDepartmentAgentsRaw.replaceUsernameOfAgentByUserId["LivechatDepartmentAgentsRaw.replaceUsernameOfAgentByUserId"]
end
style SG96 fill:#2a2a2a,stroke:#444,color:#aaa
subgraph SG97 ["packages/sha256/src/binb2hex.ts"]
binb2hex["Converts a binary array to a hexadecimal string."]
end
style SG97 fill:#2a2a2a,stroke:#444,color:#aaa
subgraph SG98 ["packages/sha256/src/core.ts"]
safeAdd["Performs safe addition for 32-bit integers to prevent overflow issues during hashing."]
ch["SHA-256 CH (choose) bitwise function."]
maj["SHA-256 MAJ (majority) bitwise function."]
sigma0256["SHA-256 Sigma0 bitwise function."]
core["Core SHA-256 hashing algorithm implementation."]
end
style SG98 fill:#2a2a2a,stroke:#444,color:#aaa
subgraph SG99 ["packages/sha256/src/sha256.ts"]
SHA256["Calculates the SHA-256 hash of an input string."]
end
style SG99 fill:#2a2a2a,stroke:#444,color:#aaa
subgraph SG100 ["packages/sha256/src/str2binb.ts"]
str2binb["Converts a string into a binary array representation."]
end
style SG100 fill:#2a2a2a,stroke:#444,color:#aaa
subgraph SG101 ["packages/sha256/src/utf8Encode.ts"]
utf8Encode["Encodes a string into UTF-8 format."]
end
style SG101 fill:#2a2a2a,stroke:#444,color:#aaa
action --> hashLoginToken
action --> Presence.setStatus
action --> isSMTPConfigured
action --> removePersonalAccessTokenOfUser
action --> regeneratePersonalAccessTokenOfUser
action --> generatePersonalAccessTokenOfUser
action --> UserChangedAuditStore.constructor
action --> hasPermissionAsync
action --> resetUserE2EEncriptionKey
action --> resetAvatar
action --> executeSetUserActiveStatus
action --> requestDataDownload
action --> sendConfirmationEmail
action --> registerUser
action --> saveUserPreferences
action --> sendForgotPasswordEmail
action --> MeteorError.constructor
action --> validateUsername
action --> deleteUser
action --> setUserAvatar
action --> getFullUserDataByUniqueSearchTerm
action --> getAvatarSuggestionForUser
action --> saveCustomFields
action --> checkUsernameAvailabilityWithValidation
action --> checkUsernameAvailability
action --> generateUsernameSuggestion
action --> saveCustomFieldsWithoutValidation
action --> validateCustomFields
action --> validateNameChars
action --> checkEmailAvailability
action --> canEditExtension
action --> ._Rocket.Chat_apps_meteor_app_lib_server_functions_saveUser_index.ts
action --> sendWelcomeEmail
action --> setUsernameWithValidation
action --> notifyOnUserChange
action --> notifyOnUserChangeAsync
action --> deleteUserOwnAccount
action --> generateAccessToken
action --> resetTOTP
action --> EmailCheck.sendEmailCode
action --> getUserForCheck
action --> getUserFromParams
action --> isUserFromParams
action --> getPaginationItems
action --> getUserInfo
action --> APIClass.success
action --> APIClass.failure
action --> APIClass.forbidden
action --> APIClass.this.parseJsonQuery
action --> ._Rocket.Chat_apps_meteor_app_api_server_lib_isValidQuery.ts
action --> getUploadFormData
action --> findUsersToAutocomplete
action --> findPaginatedUsersByStatus
action --> enableTotp
action --> disableTotp
action --> validateTotpTempToken
action --> regenerateTotpCodes
action --> codesRemainingTotp
action --> get
Presence.setStatus --> Presence.setActiveState
Presence.setStatus --> Presence.clearActiveState
Presence.setStatus --> normalizeStatusText
isSMTPConfigured --> CachedSettings.get
removePersonalAccessTokenOfUser --> removePersonalAccessTokenOfUser
removePersonalAccessTokenOfUser --> hasPermissionAsync
regeneratePersonalAccessTokenOfUser --> removePersonalAccessTokenOfUser
regeneratePersonalAccessTokenOfUser --> generatePersonalAccessTokenOfUser
regeneratePersonalAccessTokenOfUser --> hasPermissionAsync
generatePersonalAccessTokenOfUser --> hashLoginToken
generatePersonalAccessTokenOfUser --> hasPermissionAsync
hasPermissionAsync --> Authorization.hasPermission
resetUserE2EEncriptionKey --> isUserIdFederated
resetUserE2EEncriptionKey --> sendResetNotification
resetUserE2EEncriptionKey --> notifyOnUserChange
resetUserE2EEncriptionKey --> notifyOnSubscriptionChangedByUserId
resetAvatar --> hasPermissionAsync
resetAvatar --> resetAvatar
resetAvatar --> userId
resetAvatar --> method
resetAvatar --> CachedSettings.get
executeSetUserActiveStatus --> hasPermissionAsync
executeSetUserActiveStatus --> setUserActiveStatus
requestDataDownload --> getPath
requestDataDownload --> requestDataDownload
requestDataDownload --> method
requestDataDownload --> CachedSettings.get
registerUser --> generateStampedLoginToken
registerUser --> validateInviteToken
registerUser --> registerUser
registerUser --> trim
registerUser --> ._Rocket.Chat_apps_meteor_app_lib_server_index.ts
registerUser --> Accounts.insertUserDoc
registerUser --> CachedSettings.get
saveUserPreferences --> Meteor.userId
saveUserPreferences --> updateNotificationPreferences
saveUserPreferences --> saveUserPreferences
saveUserPreferences --> method
saveUserPreferences --> notifyOnUserChange
saveUserPreferences --> notifyOnSubscriptionChangedByAutoTranslateAndUserId
saveUserPreferences --> notifyOnSubscriptionChangedByUserId
saveUserPreferences --> CachedSettings.get
sendForgotPasswordEmail --> sendForgotPasswordEmail
sendForgotPasswordEmail --> CachedSettings.get
validateUsername --> CachedSettings.get
deleteUser --> LivechatDepartmentAgentsRaw.findByAgentId
deleteUser --> LivechatDepartmentAgentsRaw.removeByAgentId
deleteUser --> FileUpload.getStore
deleteUser --> shouldRemoveOrChangeOwner
deleteUser --> getSubscribedRoomsForUserWithDetails
deleteUser --> getUserSingleOwnedRooms
deleteUser --> updateGroupDMsName
deleteUser --> relinquishRoomOwnerships
deleteUser --> notifyOnRoomChangedById
deleteUser --> notifyOnIntegrationChangedByUserId
deleteUser --> notifyOnLivechatDepartmentAgentChanged
deleteUser --> notifyOnUserChange
deleteUser --> CachedSettings.get
setUserAvatar --> FileUpload.getStore
setUserAvatar --> onceTransactionCommitedSuccessfully
setUserAvatar --> RocketChatFile.dataURIParse
setUserAvatar --> CachedSettings.get
setUserAvatar --> CachedSettings.set
getFullUserDataByUniqueSearchTerm --> hasPermissionAsync
getFullUserDataByUniqueSearchTerm --> getFields
getFullUserDataByUniqueSearchTerm --> findTargetUser
getFullUserDataByUniqueSearchTerm --> CachedSettings.get
getAvatarSuggestionForUser --> CachedSettings.get
saveCustomFields --> saveCustomFieldsWithoutValidation
saveCustomFields --> validateCustomFields
saveCustomFields --> trim
saveCustomFields --> CachedSettings.get
checkUsernameAvailabilityWithValidation --> checkUsernameAvailability
checkUsernameAvailabilityWithValidation --> CachedSettings.get
checkUsernameAvailability --> toRegExp
checkUsernameAvailability --> usernameIsBlocked
checkUsernameAvailability --> validateName
generateUsernameSuggestion --> slug
generateUsernameSuggestion --> usernameIsAvailable
generateUsernameSuggestion --> name
generateUsernameSuggestion --> CachedSettings.get
saveCustomFieldsWithoutValidation --> onceTransactionCommitedSuccessfully
saveCustomFieldsWithoutValidation --> getCustomFieldsMeta
saveCustomFieldsWithoutValidation --> trim
saveCustomFieldsWithoutValidation --> notifyOnSubscriptionChangedByUserIdAndRoomType
saveCustomFieldsWithoutValidation --> CachedSettings.get
saveCustomFieldsWithoutValidation --> CachedSettings.set
validateCustomFields --> trim
validateCustomFields --> CachedSettings.get
canEditExtension --> MeteorError.constructor
canEditExtension --> CachedSettings.get
sendWelcomeEmail --> sendUserEmail
sendWelcomeEmail --> CachedSettings.get
setUsernameWithValidation --> validateUsername
setUsernameWithValidation --> checkUsernameAvailability
setUsernameWithValidation --> joinDefaultChannels
setUsernameWithValidation --> isUserInFederatedRooms
setUsernameWithValidation --> saveUserIdentity
setUsernameWithValidation --> notifyOnUserChange
setUsernameWithValidation --> CachedSettings.get
notifyOnUserChangeAsync --> notifyOnUserChange
deleteUserOwnAccount --> SHA256
deleteUserOwnAccount --> Meteor.userId
deleteUserOwnAccount --> deleteUser
deleteUserOwnAccount --> trim
deleteUserOwnAccount --> method
deleteUserOwnAccount --> deleteUserOwnAccount
deleteUserOwnAccount --> CachedSettings.get
generateAccessToken --> generateStampedLoginToken
generateAccessToken --> MeteorError.constructor
resetTOTP --> isUserIdFederated
resetTOTP --> notifyOnUserChange
resetTOTP --> sendResetNotification_2
EmailCheck.sendEmailCode --> EmailCheck.getUserVerifiedEmails
EmailCheck.sendEmailCode --> EmailCheck.send2FAEmail
EmailCheck.sendEmailCode --> CachedSettings.get
getPaginationItems --> CachedSettings.get
getUserInfo --> getURL
getUserInfo --> isVerifiedEmail
getUserInfo --> getUserPreferences
getUserInfo --> filterOutdatedVersionUpdateBanners
getUserInfo --> getUserCalendar
APIClass.success --> isObject
APIClass.failure --> isObject
APIClass.this.parseJsonQuery --> APIClass.this.parseJsonQuery
getUploadFormData --> getMimeType
getUploadFormData --> MeteorError.constructor
findUsersToAutocomplete --> hasPermissionAsync
findUsersToAutocomplete --> CachedSettings.get
findPaginatedUsersByStatus --> hasPermissionAsync
enableTotp --> TOTP.generateSecret
enableTotp --> TOTP.generateOtpauthURL
enableTotp --> requireUser
disableTotp --> notifyOnUserChange
disableTotp --> TOTP.verify
disableTotp --> requireUser
validateTotpTempToken --> hashLoginToken
validateTotpTempToken --> notifyOnUserChange
validateTotpTempToken --> notifyOnUserChangeAsync
validateTotpTempToken --> TOTP.verify
validateTotpTempToken --> TOTP.generateCodes
validateTotpTempToken --> requireUser
regenerateTotpCodes --> TOTP.verify
regenerateTotpCodes --> TOTP.generateCodes
regenerateTotpCodes --> requireUser
codesRemainingTotp --> requireUser
get --> getURL
get --> hasPermissionAsync
get --> getUserFromParams
get --> getPaginationItems
get --> APIClass.success
get --> APIClass.forbidden
get --> APIClass.this.parseJsonQuery
get --> CachedSettings.set
get --> ._Rocket.Chat_apps_meteor_app_api_server_lib_isValidQuery.ts
get --> getInclusiveFields
get --> getNonEmptyFields
get --> getNonEmptyQuery
get --> get
Presence.setActiveState --> Presence.updatePresenceAndReschedule
Presence.setActiveState --> normalizeStatusText
Presence.clearActiveState --> Presence.updatePresenceAndReschedule
CachedSettings.get --> CachedSettings.get
Authorization.hasPermission --> Authorization.all
sendResetNotification --> send
sendResetNotification --> CachedSettings.get
method --> compareVersions
method --> warn
setUserActiveStatus --> Meteor.userId
setUserActiveStatus --> executeSetUserActiveStatus
validateInviteToken --> CachedSettings.get
trim --> makeString
trim --> defaultToWhiteSpace
Accounts.insertUserDoc --> addUserRolesAsync
Accounts.insertUserDoc --> getNewUserRoles
Accounts.insertUserDoc --> setAvatarFromServiceWithValidation
Accounts.insertUserDoc --> getAvatarSuggestionForUser
Accounts.insertUserDoc --> joinDefaultChannels
Accounts.insertUserDoc --> parseCSV
Accounts.insertUserDoc --> notifyOnSettingChangedById
Accounts.insertUserDoc --> CachedSettings.get
Accounts.insertUserDoc --> SettingsRegistry.add
Meteor.userId --> watchUserId
updateNotificationPreferences --> notifyOnSubscriptionChangedByUserPreferences
LivechatDepartmentAgentsRaw.findByAgentId --> BaseRaw.find
LivechatDepartmentAgentsRaw.removeByAgentId --> BaseRaw.deleteMany
FileUpload.getStore --> FileUpload.getStoreByName
FileUpload.getStore --> FileUpload.get
getSubscribedRoomsForUserWithDetails --> ._Rocket.Chat_apps_meteor_app_authorization_server_index.ts
updateGroupDMsName --> getFname
updateGroupDMsName --> getName
updateGroupDMsName --> getUsersWhoAreInTheSameGroupDMsAs
updateGroupDMsName --> getMembers
updateGroupDMsName --> notifyOnSubscriptionChangedByRoomId
relinquishRoomOwnerships --> addUserRolesAsync
relinquishRoomOwnerships --> bulkRoomCleanUp
onceTransactionCommitedSuccessfully --> isExtendedSession
onceTransactionCommitedSuccessfully --> withError
CachedSettings.set --> CachedSettings.has
CachedSettings.set --> CachedSettings.get
CachedSettings.set --> CachedSettings.set
getFields --> getCustomFields
validateName --> CachedSettings.get
name --> slug
name --> CachedSettings.get
sendUserEmail --> send
sendUserEmail --> MeteorError.constructor
sendUserEmail --> CachedSettings.get
joinDefaultChannels --> addUserToDefaultChannels
saveUserIdentity --> onceTransactionCommitedSuccessfully
saveUserIdentity --> setRealName
saveUserIdentity --> setUsername
saveUserIdentity --> updateUsernameReferences
saveUserIdentity --> validateName
SHA256 --> utf8Encode
SHA256 --> binb2hex
SHA256 --> str2binb
SHA256 --> core
sendResetNotification_2 --> send
sendResetNotification_2 --> CachedSettings.get
EmailCheck.send2FAEmail --> replace
EmailCheck.send2FAEmail --> send
EmailCheck.send2FAEmail --> CachedSettings.get
getURL --> getURLWithoutSettings
getURL --> CachedSettings.get
getUserPreferences --> getUserPreference
getUserPreferences --> CachedSettings.getByRegexp
getUserCalendar --> CachedSettings.get
getMimeType --> getMimeTypeFromFileName
TOTP.generateSecret --> TOTP.generateSecret
TOTP.verify --> SHA256
TOTP.verify --> TOTP.verify
TOTP.verify --> CachedSettings.get
TOTP.generateCodes --> SHA256
Presence.updatePresenceAndReschedule --> Presence.setupNextExpiration
Presence.updatePresenceAndReschedule --> Presence.updateUserPresence
send --> replace
send --> wrap
send --> sendNoWrap
warn --> compareVersions
warn --> warn
defaultToWhiteSpace --> makeString
addUserRolesAsync --> syncRoomRolePriorityForUserAndRoom
addUserRolesAsync --> validateRoleList
addUserRolesAsync --> MeteorError.constructor
addUserRolesAsync --> notifyOnSubscriptionChangedByRoomIdAndUserId
getNewUserRoles --> parseCSV
getNewUserRoles --> CachedSettings.get
setAvatarFromServiceWithValidation --> hasPermissionAsync
setAvatarFromServiceWithValidation --> setUserAvatar
setAvatarFromServiceWithValidation --> CachedSettings.get
SettingsRegistry.add --> validateSetting
SettingsRegistry.add --> getSettingDefaults
SettingsRegistry.add --> CachedSettings.getSetting
SettingsRegistry.add --> CachedSettings.set
SettingsRegistry.add --> SettingsRegistry.saveUpdatedSetting
watchUserId --> watch
BaseRaw.find --> BaseRaw.doNotMixInclusionAndExclusionFields
BaseRaw.find --> BaseRaw.find
BaseRaw.deleteMany --> BaseRaw.find
BaseRaw.deleteMany --> BaseRaw.updateOne
BaseRaw.deleteMany --> BaseRaw.deleteMany
FileUpload.get --> FileUpload.getStoreByName
FileUpload.get --> FileUpload.get
bulkRoomCleanUp --> FileUpload.removeFilesByRoomId
bulkRoomCleanUp --> bulkTeamCleanup
bulkRoomCleanUp --> notifyOnSubscriptionChanged
bulkRoomCleanUp --> eraseRoomLooseValidation
CachedSettings.has --> CachedSettings.has
addUserToDefaultChannels --> getDefaultSubscriptionPref
addUserToDefaultChannels --> getSubscriptionAutotranslateDefaultConfig
addUserToDefaultChannels --> getDefaultChannels
addUserToDefaultChannels --> notifyOnSubscriptionChangedById
addUserToDefaultChannels --> CachedSettings.get
setRealName --> onceTransactionCommitedSuccessfully
setRealName --> CachedSettings.get
setRealName --> CachedSettings.set
setUsername --> onceTransactionCommitedSuccessfully
setUsername --> validateUsername
setUsername --> setUserAvatar
setUsername --> getAvatarSuggestionForUser
setUsername --> checkUsernameAvailability
setUsername --> addUserToRoom
setUsername --> isUserInFederatedRooms
setUsername --> CachedSettings.get
updateUsernameReferences --> LivechatDepartmentAgentsRaw.replaceUsernameOfAgentByUserId
updateUsernameReferences --> FileUpload.getStore
updateUsernameReferences --> updateGroupDMsName
updateUsernameReferences --> notifyOnRoomChangedByUsernamesOrUids
updateUsernameReferences --> notifyOnSubscriptionChangedByNameAndRoomType
updateUsernameReferences --> notifyOnSubscriptionChangedByUserId
core --> safeAdd
core --> ch
core --> maj
core --> sigma0256
replace --> replace
replace --> CachedSettings.get
getUserPreference --> CachedSettings.get
wrap --> replace
wrap --> CachedSettings.get
sendNoWrap --> CachedSettings.get
CachedSettings.getSetting --> CachedSettings.get
BaseRaw.updateOne --> BaseRaw.updateOne
FileUpload.removeFilesByRoomId --> FileUpload.getStore
getSubscriptionAutotranslateDefaultConfig --> CachedSettings.get
addUserToRoom --> notifyOnRoomChangedById
addUserToRoom --> notifyOnSubscriptionChanged
addUserToRoom --> CachedSettings.get
Fix with AI
A security vulnerability was found by Hacktron.
File: apps/meteor/app/api/server/v1/users.ts
Lines: 2222-2224
Severity: high
Vulnerability: Missing Rate Limiting on TOTP REST Endpoints Allows 2FA Bypass via Brute-Force
Description:
The REST API endpoints for managing TOTP 2FA (`users.totp.disable`, `users.totp.validate`, and `users.totp.regenerateCodes`) do not define `rateLimiterOptions`. While the `APIClass` does enforce a default rate limit for endpoints without explicit `rateLimiterOptions` (if the global rate limiter is enabled), this default limit (`API_Enable_Rate_Limiter_Limit_Calls_Default` and `API_Enable_Rate_Limiter_Limit_Time_Default`) is often too generous for sensitive authentication operations like TOTP code verification.
More critically, the underlying `TOTPCheck` implementation (`./Rocket.Chat/apps/meteor/app/2fa/server/code/TOTPCheck.ts`) explicitly returns `false` for `maxFaildedAttemtpsReached`, meaning there is no stateful lockout or brute-force protection at the sink level for TOTP codes (unlike email codes, which increment an invalid attempt counter).
Because the default API rate limit is not tuned for brute-force protection of a 6-digit space and there is no sink-level lockout, an attacker who has compromised a user's session can rapidly submit TOTP codes to disable 2FA or regenerate backup codes. If the default rate limit is disabled or bypassed (e.g., via the `api-bypass-rate-limit` permission or `TEST_MODE`), the endpoints are completely unprotected. Once 2FA is disabled, the attacker can perform critical actions like changing the password or email, leading to full account takeover.
Proof of Concept:
```python
import requests
import concurrent.futures
url = "https://your-rocket-chat-instance.com/api/v1/users.totp.disable"
headers = {
"X-Auth-Token": "compromised_session_token",
"X-User-Id": "compromised_user_id",
"Content-Type": "application/json"
}
def try_code(code):
payload = {"code": f"{code:06d}"}
response = requests.post(url, json=payload, headers=headers)
if response.status_code == 200 and response.json().get("disabled"):
print(f"[+] Success! 2FA disabled with code: {code:06d}")
return True
return False
# Brute-force the 6-digit space
with concurrent.futures.ThreadPoolExecutor(max_workers=50) as executor:
executor.map(try_code, range(1000000))
```
Affected Code:
API.v1
.post(
'users.totp.enable',
{
authRequired: true,
response: {
200: ajv.compile<{ secret: string; url: string }>({
type: 'object',
properties: {
secret: { type: 'string' },
url: { type: 'string' },
success: { type: 'boolean', enum: [true] },
},
required: ['secret', 'url', 'success'],
additionalProperties: false,
}),
400: validateBadRequestErrorResponse,
401: validateUnauthorizedErrorResponse,
},
},
async function action() {
return API.v1.success(await enableTotp(this.userId));
},
)
.post(
'users.totp.disable',
{
authRequired: true,
body: ajv.compile<{ code: string }>({
type: 'object',
properties: { code: { type: 'string', minLength: 1 } },
required: ['code'],
additionalProperties: false,
}),
response: {
200: ajv.compile<{ disabled: boolean }>({
type: 'object',
properties: {
disabled: { type: 'boolean' },
success: { type: 'boolean', enum: [true] },
},
required: ['disabled', 'success'],
additionalProperties: false,
}),
400: validateBadRequestErrorResponse,
401: validateUnauthorizedErrorResponse,
},
},
async function action() {
const disabled = await disableTotp(this.userId, this.bodyParams.code);
return API.v1.success({ disabled });
},
)
.post(
'users.totp.validate',
{
authRequired: true,
body: ajv.compile<{ code: string }>({
type: 'object',
properties: { code: { type: 'string', minLength: 1 } },
required: ['code'],
additionalProperties: false,
}),
response: {
200: ajv.compile<{ codes: string[] }>({
type: 'object',
properties: {
codes: { type: 'array', items: { type: 'string' } },
success: { type: 'boolean', enum: [true] },
},
required: ['codes', 'success'],
additionalProperties: false,
}),
400: validateBadRequestErrorResponse,
401: validateUnauthorizedErrorResponse,
},
},
async function action() {
const result = await validateTotpTempToken(this.userId, this.bodyParams.code, this.token);
return API.v1.success(result);
},
)
.post(
'users.totp.regenerateCodes',
{
authRequired: true,
body: ajv.compile<{ code: string }>({
type: 'object',
properties: { code: { type: 'string', minLength: 1 } },
required: ['code'],
additionalProperties: false,
}),
response: {
200: ajv.compile<{ codes: string[] }>({
type: 'object',
properties: {
codes: { type: 'array', items: { type: 'string' } },
success: { type: 'boolean', enum: [true] },
},
required: ['codes', 'success'],
additionalProperties: false,
}),
400: validateBadRequestErrorResponse,
401: validateUnauthorizedErrorResponse,
},
},
async function action() {
const result = await regenerateTotpCodes(this.userId, this.bodyParams.code);
if (!result) {
return API.v1.failure('invalid-totp');
}
return API.v1.success(result);
},
)
Acceptance criteria:
- Acceptance is defined by the **actual reported behavior**, not by tests passing.
- Reproduce the issue, or narrow the exact code path that produces it, *before* changing code. State what you confirmed.
- Fix the underlying cause. Mitigations that paper over the reported behavior do not count as a fix.
- Add a regression test that fails on the unpatched code and passes on the fix. If a regression test is genuinely impractical (e.g. race condition, infra-level issue), say so and explain why.
- Existing tests passing is **not** the bar. Do not declare done on tests-pass theatre.
Only change what is necessary to fix this vulnerability. Do not refactor adjacent code or modify unrelated files.
Triage: Reply !fp <reason> (false positive), !valid (confirmed), !accepted_risk <reason>, or !fixed (resolved). Any other reply is saved as a triage note.
Reason is optional but improves future scans — e.g. !fp internal endpoint, not user-facing.
Summary
Continues the DDP→REST sweep (#40659, #40711, #40675, #40724, #40728). This batch migrates the five
2fa:*TOTP DDP methods that backedaccount/security/TwoFactorTOTP. DDP methods stay registered for external SDK/mobile clients with deprecation logs pointing at the new routes.New endpoints
2fa:enablePOST /v1/users.totp.enable{ secret, url }2fa:disablePOST /v1/users.totp.disable{ code }{ disabled }2fa:validateTempTokenPOST /v1/users.totp.validate{ code }{ codes }2fa:regenerateCodesPOST /v1/users.totp.regenerateCodes{ code }{ codes }2fa:checkCodesRemainingGET /v1/users.totp.codesRemaining{ remaining }All five extract the original method body into a shared function (
apps/meteor/app/2fa/server/functions/totp.ts) reused by both DDP + REST entrypoints.Validate flow note
validatekeeps the DDP-era post-enable login-token rotation: REST forwards the caller'sX-Auth-Token(this.token) so non-PAT tokens get revoked just like the DDP path did viathis.connection.httpHeaders['x-auth-token'].Client changes
TwoFactorTOTP.tsxswapped fiveuseMethodhooks for fiveuseEndpointhooks. Disable response shape changed from bare boolean to{ disabled: boolean }; verify/regenerate continue to return{ codes }.Test plan
🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Bug Fixes