Skip to content

fix: new API endpoint for avatar upload#7003

Draft
Rohit3523 wants to merge 7 commits intodevelopfrom
avatar-upload-fix
Draft

fix: new API endpoint for avatar upload#7003
Rohit3523 wants to merge 7 commits intodevelopfrom
avatar-upload-fix

Conversation

@Rohit3523
Copy link
Contributor

@Rohit3523 Rohit3523 commented Feb 22, 2026

Proposed changes

This introduces the new endpoint for uploading avatar https://developer.rocket.chat/apidocs/set-user-avatar
which is also being used by web.

Pending: E2E Test for avatar upload

Issue(s)

How to test or reproduce

Screenshots

Types of changes

  • Bugfix (non-breaking change which fixes an issue)
  • Improvement (non-breaking change which improves a current function)
  • New feature (non-breaking change which adds functionality)
  • Documentation update (if none of the other choices apply)

Checklist

  • I have read the CONTRIBUTING doc
  • I have signed the CLA
  • Lint and unit tests pass locally with my changes
  • I have added tests that prove my fix is effective or that my feature works (if applicable)
  • I have added necessary documentation (if applicable)
  • Any dependent changes have been merged and published in downstream modules

Further comments

Summary by CodeRabbit

  • New Features
    • Users can now upload custom avatars directly through the application.
    • Enhanced avatar management workflow with improved authentication and security measures.
    • Added multipart form-data support for avatar file uploads to the backend service.
    • Improved error handling and server integration for avatar updates.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 22, 2026

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (3)
app/views/ChangeAvatarView/index.tsx (1)

82-90: Minor: Consider renaming selector parameter to avoid shadowing.

The selector uses state as its parameter name, which shadows the state from useReducer on line 79. While this works correctly, it could be confusing during code review or debugging.

♻️ Rename selector parameter for clarity
-const { userId, username, server, user } = useAppSelector(
-	state => ({
-		userId: getUserSelector(state).id,
-		username: getUserSelector(state).username,
-		server: state.server.server,
-		user: state.login.user
+const { userId, username, server, user } = useAppSelector(
+	appState => ({
+		userId: getUserSelector(appState).id,
+		username: getUserSelector(appState).username,
+		server: appState.server.server,
+		user: appState.login.user
 	}),
 	shallowEqual
 );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/views/ChangeAvatarView/index.tsx` around lines 82 - 90, The selector
callback passed to useAppSelector shadows the local reducer variable named
state; rename the selector parameter (e.g., from state to rootState or appState)
to avoid confusion and make the intent clearer—update the call using
useAppSelector(state => ({ ... })) to use useAppSelector(rootState => ({ userId:
getUserSelector(rootState).id, username: getUserSelector(rootState).username,
server: rootState.server.server, user: rootState.login.user }), shallowEqual)
and keep references to getUserSelector, useAppSelector and shallowEqual intact.
app/lib/services/restApi.ts (2)

732-737: Headers include redundant optional chaining.

Lines 735-736 use user?.token and user?.id, but these are already verified as truthy on line 721. This is harmless but slightly redundant.

♻️ Remove redundant optional chaining
 const headers = {
 	...RocketChatSettings.customHeaders,
 	'Content-Type': 'multipart/form-data',
-	'X-Auth-Token': user?.token,
-	'X-User-Id': user?.id
+	'X-Auth-Token': user.token,
+	'X-User-Id': user.id
 };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/lib/services/restApi.ts` around lines 732 - 737, The headers object in
restApi.ts uses redundant optional chaining for user?.token and user?.id even
though user is already validated earlier; update the headers construction (const
headers = { ...RocketChatSettings.customHeaders, 'Content-Type':
'multipart/form-data', 'X-Auth-Token': user?.token, 'X-User-Id': user?.id }) to
use user.token and user.id instead, keeping RocketChatSettings.customHeaders and
the other header keys unchanged.

721-730: Extension extraction may produce invalid MIME types for certain URLs.

The extension extraction url.split('.').pop() could produce unexpected results:

  • URLs with query strings: image.png?v=123png?v=123 → invalid MIME type
  • URLs without extensions or data URIs would default to png which may not match actual content

For this avatar upload flow via ImagePicker, the URL should typically be a clean file path. However, consider adding sanitization for robustness.

♻️ Optional: Sanitize extension extraction
 if (service === 'upload' && url && server && user?.id && user?.token) {
-	const extension = url.split('.').pop() || 'png';
+	const rawExtension = url.split('.').pop() || 'png';
+	const extension = rawExtension.split('?')[0].toLowerCase(); // Remove query params
 	const formData = [
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/lib/services/restApi.ts` around lines 721 - 730, The extension extraction
for the upload branch (the block that sets extension and formData) can produce
invalid MIME types when url contains query strings or no real extension; update
the logic in that service === 'upload' branch to sanitize the extension by
stripping query/hash fragments from url (remove anything after ? or #), validate
the resulting extension against a whitelist of known image extensions (e.g.,
png,jpg,jpeg,gif,webp) and fall back to a safe default like "png" if validation
fails; optionally, if available, prefer deriving MIME from an explicit
content-type or from the ImagePicker-provided file info instead of the url
string.
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 895ebf0 and aef6721.

📒 Files selected for processing (5)
  • app/definitions/rest/v1/users.ts
  • app/lib/methods/helpers/fileUpload/Upload.android.ts
  • app/lib/services/restApi.ts
  • app/views/ChangeAvatarView/index.tsx
  • app/views/ChangeAvatarView/submitServices.ts
🧰 Additional context used
🧬 Code graph analysis (3)
app/views/ChangeAvatarView/submitServices.ts (2)
app/definitions/IProfile.ts (1)
  • IAvatar (21-26)
app/lib/services/restApi.ts (1)
  • setAvatarFromService (702-745)
app/lib/services/restApi.ts (1)
app/lib/methods/helpers/fetch.ts (2)
  • url (42-54)
  • headers (23-27)
app/views/ChangeAvatarView/index.tsx (3)
app/lib/hooks/useAppSelector.ts (1)
  • useAppSelector (6-6)
app/selectors/login.ts (1)
  • getUserSelector (20-20)
app/views/ChangeAvatarView/submitServices.ts (1)
  • changeUserAvatar (15-21)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: format
  • GitHub Check: ESLint and Test / run-eslint-and-test
🔇 Additional comments (6)
app/definitions/rest/v1/users.ts (1)

72-74: LGTM!

The new users.setAvatar endpoint type definition is correctly structured and aligns with the Rocket.Chat API documentation. It properly defines the POST method with avatarUrl parameter for URL-based avatar setting.

app/lib/methods/helpers/fileUpload/Upload.android.ts (1)

13-13: LGTM!

The dynamic fieldName implementation correctly allows different multipart field names per upload use case. The default 'file' value maintains backward compatibility, while avatar uploads will use 'image' as expected by the Rocket.Chat API.

Also applies to: 23-23, 40-47, 62-62

app/views/ChangeAvatarView/submitServices.ts (1)

15-21: LGTM!

The updated signature correctly passes server and user context to setAvatarFromService, enabling the new upload-based avatar setting path. The spread operator cleanly merges the avatar data with authentication context.

app/lib/services/restApi.ts (2)

1-2: LGTM on new imports.

The imports for RocketChatSettings from the SDK and FileUpload helper are correctly added to support the new avatar upload functionality.

Also applies to: 30-30


713-716: The server parameter is actually always provided by the only caller in the codebase.

While server: string is indeed required and is not used in the fallback path (line 744), the only caller (app/views/ChangeAvatarView/submitServices.ts:17) always provides it via { ...avatarUpload, server, user }. There are no other callers in the codebase that would fail to provide this parameter, so backward compatibility is not an issue.

app/views/ChangeAvatarView/index.tsx (1)

164-166: LGTM on updated avatar submission.

The changeUserAvatar call correctly passes the avatar state, server URL, and user authentication context needed for the new upload-based avatar API endpoint.

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@app/lib/services/restApi.ts`:
- Around line 732-737: The headers object in restApi.ts uses redundant optional
chaining for user?.token and user?.id even though user is already validated
earlier; update the headers construction (const headers = {
...RocketChatSettings.customHeaders, 'Content-Type': 'multipart/form-data',
'X-Auth-Token': user?.token, 'X-User-Id': user?.id }) to use user.token and
user.id instead, keeping RocketChatSettings.customHeaders and the other header
keys unchanged.
- Around line 721-730: The extension extraction for the upload branch (the block
that sets extension and formData) can produce invalid MIME types when url
contains query strings or no real extension; update the logic in that service
=== 'upload' branch to sanitize the extension by stripping query/hash fragments
from url (remove anything after ? or #), validate the resulting extension
against a whitelist of known image extensions (e.g., png,jpg,jpeg,gif,webp) and
fall back to a safe default like "png" if validation fails; optionally, if
available, prefer deriving MIME from an explicit content-type or from the
ImagePicker-provided file info instead of the url string.

In `@app/views/ChangeAvatarView/index.tsx`:
- Around line 82-90: The selector callback passed to useAppSelector shadows the
local reducer variable named state; rename the selector parameter (e.g., from
state to rootState or appState) to avoid confusion and make the intent
clearer—update the call using useAppSelector(state => ({ ... })) to use
useAppSelector(rootState => ({ userId: getUserSelector(rootState).id, username:
getUserSelector(rootState).username, server: rootState.server.server, user:
rootState.login.user }), shallowEqual) and keep references to getUserSelector,
useAppSelector and shallowEqual intact.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant