Skip to content

fix(security): harden Android notification receivers, token storage, and deep-link login#7447

Open
OtavioStasiak wants to merge 13 commits into
developfrom
fix.expose-broadcast-receiver-vulnerability
Open

fix(security): harden Android notification receivers, token storage, and deep-link login#7447
OtavioStasiak wants to merge 13 commits into
developfrom
fix.expose-broadcast-receiver-vulnerability

Conversation

@OtavioStasiak

@OtavioStasiak OtavioStasiak commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Proposed changes

This PR hardens authentication and deep-link handling against a set of related token-confusion /
silent-authentication issues, and stops exporting Android notification receivers.

  1. Scope the auth token to (server, userId)

The resume token was stored under reactnativemeteor_usertoken-${userId}, with no server component.
Because two servers can legitimately share the same userId — and a malicious server can deliberately
force a collision — that storage slot was ambiguous: the last writer won, and a lookup by a different
server could resolve a token belonging to another server, enabling token confusion / exfiltration.

The token is now keyed by reactnativemeteor_usertoken-${server}-${userId} via a shared
getUserTokenKey(server, userId) helper, used consistently across the login, server-select, and logout
paths, and mirrored on the native side (MMKV.swift on iOS, Ejson.java on Android).

A one-time, two-pass migration in the init saga moves existing tokens from the legacy slot to the new
server-scoped slot (copy every server first, then remove legacy slots, so a userId shared by multiple
servers isn't dropped mid-migration). Logout removes both the new and the legacy slots.

  1. Confirm deep-link login before consuming a resume token

A rocketchat://auth?host=…&token=… deep link could silently authenticate the user into an arbitrary
server, and any app or web page can fire it. Before consuming a resume token for a server the user
isn't already signed in to, the app now asks for explicit confirmation and lands on the login screen if
declined. (Auto-confirmed under RUNNING_E2E_TESTS so the E2E harness, which bootstraps via deep link,
isn't blocked by the native alert.)

  1. Stop exporting Android notification broadcast receivers

The notification receivers in AndroidManifest.xml are no longer exported, closing them off from other
apps on the device.


Notes:

  • Under Types of changes, tick Bugfix (it's a security fix).
  • I left the deep-link copy strings (Deep_link_login_title / Deep_link_login_description) as-is —
    they're added in app/i18n/locales/en.json.
  • Want me to draft the How to test or reproduce section too? I can write repro steps for both the
    token-scoping and the deep-link-confirmation behaviors.

Issue(s)

https://rocketchat.atlassian.net/browse/NATIVE-1342

How to test or reproduce

Deep-link login confirmation

  1. On a device/simulator, be logged out of (or not connected to) some server B.
  2. Fire a resume-token deep link for it, e.g. xcrun simctl openurl booted
    "rocketchat://auth?host=your.server.com&token=" (or the equivalent adb shell am
    start).
  3. Expected: a confirmation alert appears ("log in to host?"). Confirming logs you in; cancelling
    leaves you on the login/outside screen with no session. Before this PR the login happened silently.

Token scoped to (server, userId)

  1. Log in to two different servers that share the same userId.
  2. Switch between them (Servers list) and confirm each stays authenticated on its own account — no
    session bleed between servers.
  3. Migration check: on a build that predates this change, log in and background-kill the app; install
    this build over it and relaunch. Expected: you stay logged in (token migrated from the legacy slot to
    the server-scoped one), and logging out clears it fully.

Affected screens: deep-link entry (confirmation alert), Servers list / server switching, and the
login/logout flow. No visual changes beyond the new confirmation alert.

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

    • Added localized deep-link “sign in to this server” prompt text.
    • Added user confirmation before resuming login when a deep link targets a different server (with special handling for E2E runs).
  • Bug Fixes

    • Ensured deep-link resume and logout/session selection use server-specific saved session tokens on Android and iOS.
    • Added a one-time migration to convert legacy token storage to server-scoped storage and run it during app restore/selection.
  • Security

    • Restricted notification reply/dismiss actions to internal use only (non-exported receivers).

@OtavioStasiak OtavioStasiak temporarily deployed to approve_e2e_testing June 25, 2026 23:48 — with GitHub Actions Inactive
@coderabbitai

coderabbitai Bot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: bde5970b-b1a3-4df5-b140-9a8a723533f4

📥 Commits

Reviewing files that changed from the base of the PR and between 473d490 and 9f209ae.

📒 Files selected for processing (3)
  • .maestro/tests/assorted/deeplink.yaml
  • app/sagas/__tests__/deepLinking.test.ts
  • app/sagas/deepLinking.js
🚧 Files skipped from review as they are similar to previous changes (2)
  • .maestro/tests/assorted/deeplink.yaml
  • app/sagas/deepLinking.js
📜 Recent review details
⏰ Context from checks skipped due to timeout. (1)
  • GitHub Check: ESLint and Test / run-eslint-and-test
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{js,ts,jsx,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{js,ts,jsx,tsx}: Use descriptive names for functions, variables, and classes that clearly convey their purpose
Write comments that explain the 'why' behind code decisions, not the 'what'
Keep functions small and focused on a single responsibility
Use const by default, let when reassignment is needed, and avoid var
Prefer async/await over .then() chains for handling asynchronous operations
Use explicit error handling with try/catch blocks for async operations
Avoid deeply nested code; refactor complex logic into helper functions

Files:

  • app/sagas/__tests__/deepLinking.test.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Use TypeScript for type safety; add explicit type annotations to function parameters and return types
Prefer interfaces over type aliases for defining object shapes in TypeScript
Use enums for sets of related constants rather than magic strings or numbers

Use TypeScript with strict mode enabled

Files:

  • app/sagas/__tests__/deepLinking.test.ts
**/*.{js,jsx,ts,tsx,json}

📄 CodeRabbit inference engine (CLAUDE.md)

Use Prettier formatting with tabs, single quotes, 130 character line width, no trailing commas, and avoid arrow function parentheses

Files:

  • app/sagas/__tests__/deepLinking.test.ts
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Enforce ESLint rules from @rocket.chat/eslint-config with React, React Native, TypeScript, and Jest plugins

Files:

  • app/sagas/__tests__/deepLinking.test.ts
app/sagas/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Place sagas in 'app/sagas/' directory for handling side effects

Files:

  • app/sagas/__tests__/deepLinking.test.ts
🧠 Learnings (2)
📚 Learning: 2026-04-30T17:07:51.020Z
Learnt from: diegolmello
Repo: RocketChat/Rocket.Chat.ReactNative PR: 7274
File: app/lib/services/voip/MediaCallEvents.ts:0-0
Timestamp: 2026-04-30T17:07:51.020Z
Learning: In this Rocket.Chat React Native codebase, the ESLint rule `no-void: error` is enforced. When you see a promise returned from an async call that is not awaited (a “floating promise”), do not silence it with the `void somePromise()` pattern. Instead, handle the promise explicitly by attaching `.catch(...)` (or otherwise awaiting/handling the error) so unhandled-rejection risks are addressed in a way that satisfies the existing ESLint configuration.

Applied to files:

  • app/sagas/__tests__/deepLinking.test.ts
📚 Learning: 2026-06-25T18:37:25.526Z
Learnt from: diegolmello
Repo: RocketChat/Rocket.Chat.ReactNative PR: 7434
File: app/views/ScreenLockConfigView.test.tsx:16-22
Timestamp: 2026-06-25T18:37:25.526Z
Learning: In Rocket.Chat ReactNative tests that mock selectors for `useAppSelector`, don’t require the mocked selector input to be typed as `IApplicationState` when the fixture only includes a partial Redux state slice (e.g., only `server` and `settings`). Requiring the full `IApplicationState` type in that scenario forces unsafe `as IApplicationState` casts and undermines type-safety. For these narrowly scoped selector-mock fixtures, use a less strict type (e.g., `any`) to keep the mock focused on the slice under test.

Applied to files:

  • app/sagas/__tests__/deepLinking.test.ts
🔇 Additional comments (1)
app/sagas/__tests__/deepLinking.test.ts (1)

291-325: LGTM!


Walkthrough

Tokens now use server-scoped keys across JS, Android, and iOS, with migration and legacy fallback support. Deep-link resume login now shows a confirmation prompt before proceeding, and Android notification receivers are no longer exported.

Changes

Token scoping and migration

Layer / File(s) Summary
Shared token key helper and JS callers
app/lib/constants/keys.ts, app/sagas/login.js, app/lib/methods/logout.ts, app/sagas/selectServer.ts
Adds getUserTokenKey(server, userId) and switches login, logout, and server-selection token access to the server-scoped key.
Token migration on restore and server select
app/lib/methods/migrateTokenKeysToServerScoped.ts, app/lib/methods/migrateTokenKeysToServerScoped.test.ts, app/sagas/init.js, app/sagas/selectServer.ts
Adds the one-time migration for legacy token keys, runs it during restore and server selection, and keeps the migration flag and legacy cleanup flow.
Android and iOS token lookup
android/app/src/main/java/chat/rocket/reactnative/notification/Ejson.java, android/app/src/main/AndroidManifest.xml, ios/Shared/RocketChat/MMKV.swift, ios/Shared/RocketChat/Storage.swift, ios/Watch/WatchConnection.swift
Android and iOS now read server-scoped user tokens with legacy fallback, and Android notification receivers are marked non-exported.

Deep-link login confirmation

Layer / File(s) Summary
Deep-link resume confirmation
app/sagas/deepLinking.js, app/sagas/__tests__/deepLinking.test.ts, app/i18n/locales/*.json, .maestro/tests/assorted/deeplink.yaml
Adds a confirmation prompt before consuming a deep-link resume token, updates localized copy, and adds coverage for the decline and confirm paths.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related PRs

Suggested labels: type: bug, area: authentication

Suggested reviewers: diegolmello, Rohit3523

🚥 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 matches the PR’s main changes: Android receiver hardening, server-scoped token storage, and deep-link login confirmation.
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.

Warning

Review ran into problems

🔥 Problems

Errors were encountered while retrieving linked issues.

Errors (1)
  • NATIVE-1342: Request failed with status code 401

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.

@OtavioStasiak OtavioStasiak temporarily deployed to approve_e2e_testing July 1, 2026 18:46 — with GitHub Actions Inactive
@OtavioStasiak OtavioStasiak marked this pull request as ready for review July 1, 2026 19:59

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
app/lib/methods/logout.ts (1)

76-80: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Keep the legacy fallback before server removal.

If migration has not run or failed, resume is undefined, sdk.login({ resume }) fails, and the server-side push token cleanup is skipped before local data is deleted. Fall back to ${TOKEN_KEY}-${userId} here as well.

Proposed fix
-			const resume = UserPreferences.getString(getUserTokenKey(server, userId));
+			const resume = UserPreferences.getString(getUserTokenKey(server, userId)) ?? UserPreferences.getString(`${TOKEN_KEY}-${userId}`);
+			if (!resume) {
+				throw new Error('Missing resume token for server removal');
+			}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/lib/methods/logout.ts` around lines 76 - 80, The logout flow in `logout`
uses only `getUserTokenKey(server, userId)` for `resume`, so when migration
hasn’t run the `RocketchatClient.login` call fails and token cleanup is skipped.
Update the `resume` lookup to fall back to the legacy `${TOKEN_KEY}-${userId}`
key before calling `sdk.login({ resume })`, keeping the existing cleanup flow in
`app/lib/methods/logout.ts` intact.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app/sagas/init.js`:
- Around line 31-60: The migration in migrateTokenKeysToServerScoped is
incorrectly copying a shared legacy token into every server-scoped slot when
multiple servers reuse the same userId. Update the first pass to detect
ambiguous userId mappings and skip migrating those legacy tokens into new slots,
while still removing the legacy token afterward so affected sessions
re-authenticate safely. Keep the fix localized to migrateTokenKeysToServerScoped
and its use of UserPreferences, TOKEN_KEY_SERVER_SCOPED_MIGRATED, and
getUserTokenKey.

In `@app/sagas/selectServer.ts`:
- Line 174: The server token lookup in selectServerRequest/selectServer should
handle legacy sessions before migration has completed. Update the token read
around UserPreferences.getString(getUserTokenKey(server, userId)) to fall back
to the legacy token key when the server-scoped value is missing, or otherwise
gate the deep-link/share-extension callers until appInit() finishes migration.
Keep the change localized to the token resolution path so direct dispatches
still succeed for legacy users.

---

Outside diff comments:
In `@app/lib/methods/logout.ts`:
- Around line 76-80: The logout flow in `logout` uses only
`getUserTokenKey(server, userId)` for `resume`, so when migration hasn’t run the
`RocketchatClient.login` call fails and token cleanup is skipped. Update the
`resume` lookup to fall back to the legacy `${TOKEN_KEY}-${userId}` key before
calling `sdk.login({ resume })`, keeping the existing cleanup flow in
`app/lib/methods/logout.ts` intact.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 124eb5ef-1af7-4051-a141-833d7f08c334

📥 Commits

Reviewing files that changed from the base of the PR and between 9b34a32 and ccadcd0.

⛔ Files ignored due to path filters (1)
  • app/containers/markdown/__snapshots__/Markdown.test.tsx.snap is excluded by !**/*.snap
📒 Files selected for processing (13)
  • android/app/src/main/AndroidManifest.xml
  • android/app/src/main/java/chat/rocket/reactnative/notification/Ejson.java
  • app/i18n/locales/en.json
  • app/lib/constants/keys.ts
  • app/lib/methods/logout.ts
  • app/sagas/__tests__/deepLinking.test.ts
  • app/sagas/deepLinking.js
  • app/sagas/init.js
  • app/sagas/login.js
  • app/sagas/selectServer.ts
  • ios/Shared/RocketChat/MMKV.swift
  • ios/Shared/RocketChat/Storage.swift
  • ios/Watch/WatchConnection.swift
📜 Review details
⏰ Context from checks skipped due to timeout. (4)
  • GitHub Check: E2E Build Android / android-build
  • GitHub Check: E2E Build iOS / ios-build
  • GitHub Check: Build Android / Build
  • GitHub Check: Build iOS / Build
🧰 Additional context used
📓 Path-based instructions (6)
**/*.{js,jsx,ts,tsx,json}

📄 CodeRabbit inference engine (CLAUDE.md)

Use Prettier formatting with tabs, single quotes, 130 character line width, no trailing commas, and avoid arrow function parentheses

Files:

  • app/i18n/locales/en.json
  • app/lib/constants/keys.ts
  • app/sagas/selectServer.ts
  • app/sagas/init.js
  • app/lib/methods/logout.ts
  • app/sagas/deepLinking.js
  • app/sagas/__tests__/deepLinking.test.ts
  • app/sagas/login.js
app/i18n/**/*.{ts,tsx,json}

📄 CodeRabbit inference engine (CLAUDE.md)

Place internationalization (i18n) configuration in 'app/i18n/' directory with support for 40+ locales and RTL

Files:

  • app/i18n/locales/en.json
**/*.{js,ts,jsx,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{js,ts,jsx,tsx}: Use descriptive names for functions, variables, and classes that clearly convey their purpose
Write comments that explain the 'why' behind code decisions, not the 'what'
Keep functions small and focused on a single responsibility
Use const by default, let when reassignment is needed, and avoid var
Prefer async/await over .then() chains for handling asynchronous operations
Use explicit error handling with try/catch blocks for async operations
Avoid deeply nested code; refactor complex logic into helper functions

Files:

  • app/lib/constants/keys.ts
  • app/sagas/selectServer.ts
  • app/sagas/init.js
  • app/lib/methods/logout.ts
  • app/sagas/deepLinking.js
  • app/sagas/__tests__/deepLinking.test.ts
  • app/sagas/login.js
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Use TypeScript for type safety; add explicit type annotations to function parameters and return types
Prefer interfaces over type aliases for defining object shapes in TypeScript
Use enums for sets of related constants rather than magic strings or numbers

Use TypeScript with strict mode enabled

Files:

  • app/lib/constants/keys.ts
  • app/sagas/selectServer.ts
  • app/lib/methods/logout.ts
  • app/sagas/__tests__/deepLinking.test.ts
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Enforce ESLint rules from @rocket.chat/eslint-config with React, React Native, TypeScript, and Jest plugins

Files:

  • app/lib/constants/keys.ts
  • app/sagas/selectServer.ts
  • app/sagas/init.js
  • app/lib/methods/logout.ts
  • app/sagas/deepLinking.js
  • app/sagas/__tests__/deepLinking.test.ts
  • app/sagas/login.js
app/sagas/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Place sagas in 'app/sagas/' directory for handling side effects

Files:

  • app/sagas/selectServer.ts
  • app/sagas/__tests__/deepLinking.test.ts
🧠 Learnings (4)
📚 Learning: 2026-04-30T17:07:51.020Z
Learnt from: diegolmello
Repo: RocketChat/Rocket.Chat.ReactNative PR: 7274
File: app/lib/services/voip/MediaCallEvents.ts:0-0
Timestamp: 2026-04-30T17:07:51.020Z
Learning: In this Rocket.Chat React Native codebase, the ESLint rule `no-void: error` is enforced. When you see a promise returned from an async call that is not awaited (a “floating promise”), do not silence it with the `void somePromise()` pattern. Instead, handle the promise explicitly by attaching `.catch(...)` (or otherwise awaiting/handling the error) so unhandled-rejection risks are addressed in a way that satisfies the existing ESLint configuration.

Applied to files:

  • app/lib/constants/keys.ts
  • app/sagas/selectServer.ts
  • app/lib/methods/logout.ts
  • app/sagas/__tests__/deepLinking.test.ts
📚 Learning: 2026-05-07T13:19:52.152Z
Learnt from: diegolmello
Repo: RocketChat/Rocket.Chat.ReactNative PR: 7304
File: app/sagas/deepLinking.js:237-243
Timestamp: 2026-05-07T13:19:52.152Z
Learning: In this codebase’s Redux-Saga usage, remember that `yield put(action)` dispatches through the Redux store synchronously, and any saga(s) that synchronously react via action listeners (and synchronous `put` chains) will run to completion before the calling saga resumes at its next `yield`. As a result, within a single saga there is no scheduler interleaving between a `yield select(...)` and a subsequent `yield take(...)` at the next `yield` point, so a check-then-take pattern like `const state = yield select(...); if (state !== TARGET) { yield take(a => a.type === TARGET); }` is safe from TOCTOU races under the synchronous `put`/take model described above.

Applied to files:

  • app/sagas/init.js
  • app/sagas/deepLinking.js
  • app/sagas/login.js
📚 Learning: 2026-06-15T16:17:10.741Z
Learnt from: diegolmello
Repo: RocketChat/Rocket.Chat.ReactNative PR: 7398
File: android/app/src/main/java/chat/rocket/reactnative/notification/Encryption.java:211-211
Timestamp: 2026-06-15T16:17:10.741Z
Learning: When querying the Android SQLCipher WatermelonDB `subscriptions` table, treat the table’s primary-key column `id` as the Rocket.Chat room id (`rid`). In other words, a clause like `WHERE id = ?` should bind `ejson.rid` (this is equivalent to the iOS-side `WHERE rid = ?`). Do not flag this as a column-mismatch issue, because SQLite `subscriptions.id` stores the same value as the Rocket.Chat `rid` field.

Applied to files:

  • android/app/src/main/java/chat/rocket/reactnative/notification/Ejson.java
📚 Learning: 2026-06-25T18:37:25.526Z
Learnt from: diegolmello
Repo: RocketChat/Rocket.Chat.ReactNative PR: 7434
File: app/views/ScreenLockConfigView.test.tsx:16-22
Timestamp: 2026-06-25T18:37:25.526Z
Learning: In Rocket.Chat ReactNative tests that mock selectors for `useAppSelector`, don’t require the mocked selector input to be typed as `IApplicationState` when the fixture only includes a partial Redux state slice (e.g., only `server` and `settings`). Requiring the full `IApplicationState` type in that scenario forces unsafe `as IApplicationState` casts and undermines type-safety. For these narrowly scoped selector-mock fixtures, use a less strict type (e.g., `any`) to keep the mock focused on the slice under test.

Applied to files:

  • app/sagas/__tests__/deepLinking.test.ts
🔇 Additional comments (13)
app/i18n/locales/en.json (1)

233-234: LGTM!

app/sagas/deepLinking.js (1)

24-24: LGTM!

Also applies to: 41-64, 270-276

app/sagas/__tests__/deepLinking.test.ts (1)

85-90: LGTM!

Also applies to: 109-114, 316-335

app/lib/constants/keys.ts (1)

26-36: LGTM!

app/lib/methods/logout.ts (1)

11-18: LGTM!

Also applies to: 27-29

app/sagas/init.js (1)

5-5: LGTM!

Also applies to: 67-70

android/app/src/main/java/chat/rocket/reactnative/notification/Ejson.java (1)

138-178: LGTM!

ios/Watch/WatchConnection.swift (1)

44-44: LGTM!

app/sagas/login.js (1)

25-25: LGTM!

Also applies to: 344-344

app/sagas/selectServer.ts (1)

32-32: LGTM!

android/app/src/main/AndroidManifest.xml (1)

105-117: LGTM!

ios/Shared/RocketChat/MMKV.swift (1)

21-31: LGTM!

ios/Shared/RocketChat/Storage.swift (1)

15-15: LGTM!

Comment thread app/sagas/init.js Outdated
Comment thread app/sagas/selectServer.ts
@github-actions

github-actions Bot commented Jul 1, 2026

Copy link
Copy Markdown

iOS Build Available

Rocket.Chat 4.75.0.109259

@github-actions

github-actions Bot commented Jul 1, 2026

Copy link
Copy Markdown

@github-actions

github-actions Bot commented Jul 1, 2026

Copy link
Copy Markdown

@github-actions

github-actions Bot commented Jul 1, 2026

Copy link
Copy Markdown

iOS Build Available

Rocket.Chat 4.75.0.109263

@julio-rocketchat julio-rocketchat left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It looks good to me. It seems like all the gaps that could lead to security issues have been closed by this PR. Tks.

Comment thread app/i18n/locales/en.json
Comment thread app/lib/constants/keys.ts
Comment thread app/sagas/deepLinking.js Outdated
Comment thread app/sagas/deepLinking.js Outdated
Comment thread app/sagas/deepLinking.js Outdated
Comment thread android/app/src/main/java/chat/rocket/reactnative/notification/Ejson.java Outdated
Comment thread android/app/src/main/java/chat/rocket/reactnative/notification/Ejson.java Outdated
Comment thread android/app/src/main/AndroidManifest.xml Outdated
Comment thread app/sagas/__tests__/deepLinking.test.ts Outdated
Comment thread app/sagas/__tests__/deepLinking.test.ts Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
app/lib/methods/migrateTokenKeysToServerScoped.test.ts (1)

77-89: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a legacy-slot assertion here.

This test verifies the scoped key isn't overwritten but doesn't assert the fate of ${TOKEN_KEY}-${userId}. Given the removal gap flagged in migrateTokenKeysToServerScoped.ts, adding expect(UserPreferences.getString(${TOKEN_KEY}-${userId})).toBeNull() would pin the intended behavior once the source fix lands.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/lib/methods/migrateTokenKeysToServerScoped.test.ts` around lines 77 - 89,
The migration test currently only checks that the existing server-scoped token
is preserved, but it does not verify what happens to the legacy user-scoped
slot. In migrateTokenKeysToServerScoped.test.ts within the “does not overwrite
an existing server-scoped token” case, add an assertion for the
`${TOKEN_KEY}-${userId}` entry so the test pins the intended cleanup behavior
after migrateTokenKeysToServerScoped runs, using UserPreferences.getString on
that legacy key and expecting it to be null.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app/lib/methods/migrateTokenKeysToServerScoped.ts`:
- Around line 42-53: The migration in migrateTokenKeysToServerScoped leaves the
legacy user token slot behind when getUserTokenKey(server, userId) already
exists, because legacyKey is only tracked when a copy happens. Update the
single-server branch so the legacy ${TOKEN_KEY}-${userId} key is always added to
legacyKeys (or otherwise removed) whenever that legacy slot is processed, even
if the server-scoped key already has a value, and keep the final
legacyKeys.forEach cleanup in place. Consider tightening the existing “does not
overwrite an existing server-scoped token” test to assert the legacy key is
removed too.

---

Nitpick comments:
In `@app/lib/methods/migrateTokenKeysToServerScoped.test.ts`:
- Around line 77-89: The migration test currently only checks that the existing
server-scoped token is preserved, but it does not verify what happens to the
legacy user-scoped slot. In migrateTokenKeysToServerScoped.test.ts within the
“does not overwrite an existing server-scoped token” case, add an assertion for
the `${TOKEN_KEY}-${userId}` entry so the test pins the intended cleanup
behavior after migrateTokenKeysToServerScoped runs, using
UserPreferences.getString on that legacy key and expecting it to be null.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 5552ba1c-0d7a-47ae-8a5b-09b197d37a07

📥 Commits

Reviewing files that changed from the base of the PR and between f46aea1 and 109a79c.

📒 Files selected for processing (34)
  • android/app/src/main/AndroidManifest.xml
  • android/app/src/main/java/chat/rocket/reactnative/notification/Ejson.java
  • app/i18n/locales/ar.json
  • app/i18n/locales/bn-IN.json
  • app/i18n/locales/cs.json
  • app/i18n/locales/de.json
  • app/i18n/locales/es.json
  • app/i18n/locales/fi.json
  • app/i18n/locales/fr.json
  • app/i18n/locales/hi-IN.json
  • app/i18n/locales/hu.json
  • app/i18n/locales/it.json
  • app/i18n/locales/ja.json
  • app/i18n/locales/nl.json
  • app/i18n/locales/nn.json
  • app/i18n/locales/no.json
  • app/i18n/locales/pt-BR.json
  • app/i18n/locales/pt-PT.json
  • app/i18n/locales/ru.json
  • app/i18n/locales/sl-SI.json
  • app/i18n/locales/sv.json
  • app/i18n/locales/ta-IN.json
  • app/i18n/locales/te-IN.json
  • app/i18n/locales/tr.json
  • app/i18n/locales/zh-CN.json
  • app/i18n/locales/zh-TW.json
  • app/lib/constants/keys.ts
  • app/lib/methods/migrateTokenKeysToServerScoped.test.ts
  • app/lib/methods/migrateTokenKeysToServerScoped.ts
  • app/sagas/__tests__/deepLinking.test.ts
  • app/sagas/deepLinking.js
  • app/sagas/init.js
  • app/sagas/selectServer.ts
  • ios/Shared/RocketChat/MMKV.swift
💤 Files with no reviewable changes (3)
  • app/sagas/tests/deepLinking.test.ts
  • android/app/src/main/AndroidManifest.xml
  • app/sagas/deepLinking.js
✅ Files skipped from review due to trivial changes (19)
  • app/i18n/locales/zh-TW.json
  • app/i18n/locales/hu.json
  • app/i18n/locales/zh-CN.json
  • app/i18n/locales/it.json
  • app/i18n/locales/de.json
  • app/i18n/locales/ta-IN.json
  • app/i18n/locales/es.json
  • app/i18n/locales/cs.json
  • app/i18n/locales/hi-IN.json
  • app/i18n/locales/tr.json
  • app/i18n/locales/fr.json
  • app/i18n/locales/ar.json
  • app/i18n/locales/ja.json
  • app/i18n/locales/nl.json
  • app/i18n/locales/pt-BR.json
  • app/i18n/locales/nn.json
  • app/i18n/locales/pt-PT.json
  • app/i18n/locales/ru.json
  • app/i18n/locales/fi.json
🚧 Files skipped from review as they are similar to previous changes (4)
  • app/lib/constants/keys.ts
  • app/sagas/selectServer.ts
  • ios/Shared/RocketChat/MMKV.swift
  • android/app/src/main/java/chat/rocket/reactnative/notification/Ejson.java
📜 Review details
⏰ Context from checks skipped due to timeout. (1)
  • GitHub Check: ESLint and Test / run-eslint-and-test
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{js,jsx,ts,tsx,json}

📄 CodeRabbit inference engine (CLAUDE.md)

Use Prettier formatting with tabs, single quotes, 130 character line width, no trailing commas, and avoid arrow function parentheses

Files:

  • app/i18n/locales/te-IN.json
  • app/lib/methods/migrateTokenKeysToServerScoped.test.ts
  • app/i18n/locales/no.json
  • app/i18n/locales/bn-IN.json
  • app/i18n/locales/sv.json
  • app/i18n/locales/sl-SI.json
  • app/sagas/init.js
  • app/lib/methods/migrateTokenKeysToServerScoped.ts
app/i18n/**/*.{ts,tsx,json}

📄 CodeRabbit inference engine (CLAUDE.md)

Place internationalization (i18n) configuration in 'app/i18n/' directory with support for 40+ locales and RTL

Files:

  • app/i18n/locales/te-IN.json
  • app/i18n/locales/no.json
  • app/i18n/locales/bn-IN.json
  • app/i18n/locales/sv.json
  • app/i18n/locales/sl-SI.json
**/*.{js,ts,jsx,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{js,ts,jsx,tsx}: Use descriptive names for functions, variables, and classes that clearly convey their purpose
Write comments that explain the 'why' behind code decisions, not the 'what'
Keep functions small and focused on a single responsibility
Use const by default, let when reassignment is needed, and avoid var
Prefer async/await over .then() chains for handling asynchronous operations
Use explicit error handling with try/catch blocks for async operations
Avoid deeply nested code; refactor complex logic into helper functions

Files:

  • app/lib/methods/migrateTokenKeysToServerScoped.test.ts
  • app/sagas/init.js
  • app/lib/methods/migrateTokenKeysToServerScoped.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Use TypeScript for type safety; add explicit type annotations to function parameters and return types
Prefer interfaces over type aliases for defining object shapes in TypeScript
Use enums for sets of related constants rather than magic strings or numbers

Use TypeScript with strict mode enabled

Files:

  • app/lib/methods/migrateTokenKeysToServerScoped.test.ts
  • app/lib/methods/migrateTokenKeysToServerScoped.ts
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Enforce ESLint rules from @rocket.chat/eslint-config with React, React Native, TypeScript, and Jest plugins

Files:

  • app/lib/methods/migrateTokenKeysToServerScoped.test.ts
  • app/sagas/init.js
  • app/lib/methods/migrateTokenKeysToServerScoped.ts
🧠 Learnings (3)
📚 Learning: 2026-04-30T17:07:51.020Z
Learnt from: diegolmello
Repo: RocketChat/Rocket.Chat.ReactNative PR: 7274
File: app/lib/services/voip/MediaCallEvents.ts:0-0
Timestamp: 2026-04-30T17:07:51.020Z
Learning: In this Rocket.Chat React Native codebase, the ESLint rule `no-void: error` is enforced. When you see a promise returned from an async call that is not awaited (a “floating promise”), do not silence it with the `void somePromise()` pattern. Instead, handle the promise explicitly by attaching `.catch(...)` (or otherwise awaiting/handling the error) so unhandled-rejection risks are addressed in a way that satisfies the existing ESLint configuration.

Applied to files:

  • app/lib/methods/migrateTokenKeysToServerScoped.test.ts
  • app/lib/methods/migrateTokenKeysToServerScoped.ts
📚 Learning: 2026-06-25T18:37:25.526Z
Learnt from: diegolmello
Repo: RocketChat/Rocket.Chat.ReactNative PR: 7434
File: app/views/ScreenLockConfigView.test.tsx:16-22
Timestamp: 2026-06-25T18:37:25.526Z
Learning: In Rocket.Chat ReactNative tests that mock selectors for `useAppSelector`, don’t require the mocked selector input to be typed as `IApplicationState` when the fixture only includes a partial Redux state slice (e.g., only `server` and `settings`). Requiring the full `IApplicationState` type in that scenario forces unsafe `as IApplicationState` casts and undermines type-safety. For these narrowly scoped selector-mock fixtures, use a less strict type (e.g., `any`) to keep the mock focused on the slice under test.

Applied to files:

  • app/lib/methods/migrateTokenKeysToServerScoped.test.ts
📚 Learning: 2026-05-07T13:19:52.152Z
Learnt from: diegolmello
Repo: RocketChat/Rocket.Chat.ReactNative PR: 7304
File: app/sagas/deepLinking.js:237-243
Timestamp: 2026-05-07T13:19:52.152Z
Learning: In this codebase’s Redux-Saga usage, remember that `yield put(action)` dispatches through the Redux store synchronously, and any saga(s) that synchronously react via action listeners (and synchronous `put` chains) will run to completion before the calling saga resumes at its next `yield`. As a result, within a single saga there is no scheduler interleaving between a `yield select(...)` and a subsequent `yield take(...)` at the next `yield` point, so a check-then-take pattern like `const state = yield select(...); if (state !== TARGET) { yield take(a => a.type === TARGET); }` is safe from TOCTOU races under the synchronous `put`/take model described above.

Applied to files:

  • app/sagas/init.js
🔇 Additional comments (6)
app/i18n/locales/bn-IN.json (1)

216-217: LGTM!

app/i18n/locales/no.json (1)

220-221: LGTM!

app/i18n/locales/sl-SI.json (1)

181-182: LGTM!

app/i18n/locales/sv.json (1)

199-200: LGTM!

app/i18n/locales/te-IN.json (1)

215-216: LGTM!

app/sagas/init.js (1)

27-27: LGTM!

Comment thread app/lib/methods/migrateTokenKeysToServerScoped.ts
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants