Skip to content

fix: missing validation for external members#37050

Merged
ggazzo merged 2 commits intodevelopfrom
fix/create-channel-members-validation
Sep 24, 2025
Merged

fix: missing validation for external members#37050
ggazzo merged 2 commits intodevelopfrom
fix/create-channel-members-validation

Conversation

@juliajforesti
Copy link
Copy Markdown
Contributor

@juliajforesti juliajforesti commented Sep 24, 2025

FDR-161

Proposed changes (including videos or screenshots)

  • validate adding external members on CreateChannelModal when room is not federated
Screenshot 2025-09-24 at 10 19 26

Issue(s)

Steps to test or reproduce

Further comments

Summary by CodeRabbit

  • New Features

    • Inline validation when creating channels: blocks adding external users to non‑federated rooms and shows a clear error message.
    • Added a new invite count message to clarify how many people will be invited.
  • Bug Fixes

    • Prevents creation attempts that include external users in non‑federated rooms.
  • Chores

    • Updated external dependencies to the latest patch versions for stability and compatibility.

@dionisio-bot
Copy link
Copy Markdown
Contributor

dionisio-bot bot commented Sep 24, 2025

Looks like this PR is ready to merge! 🎉
If you have any trouble, please check the PR guidelines

@changeset-bot
Copy link
Copy Markdown

changeset-bot bot commented Sep 24, 2025

🦋 Changeset detected

Latest commit: 1819393

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 41 packages
Name Type
@rocket.chat/i18n Patch
@rocket.chat/meteor Patch
@rocket.chat/mock-providers Patch
@rocket.chat/ui-contexts Patch
@rocket.chat/web-ui-registration Patch
@rocket.chat/omnichannel-transcript Patch
@rocket.chat/omnichannel-services Patch
@rocket.chat/fuselage-ui-kit Patch
@rocket.chat/ui-client Patch
@rocket.chat/ui-voip Patch
@rocket.chat/uikit-playground Patch
@rocket.chat/gazzodown Patch
@rocket.chat/livechat Patch
@rocket.chat/ui-avatar Patch
@rocket.chat/ui-video-conf Patch
@rocket.chat/queue-worker Patch
@rocket.chat/core-typings Patch
@rocket.chat/rest-typings Patch
@rocket.chat/api-client Patch
@rocket.chat/apps Patch
@rocket.chat/core-services Patch
@rocket.chat/cron Patch
@rocket.chat/ddp-client Patch
@rocket.chat/freeswitch Patch
@rocket.chat/http-router Patch
@rocket.chat/model-typings Patch
@rocket.chat/account-service Patch
@rocket.chat/authorization-service Patch
@rocket.chat/ddp-streamer Patch
@rocket.chat/presence-service Patch
@rocket.chat/stream-hub-service Patch
@rocket.chat/license Patch
@rocket.chat/media-calls Patch
@rocket.chat/pdf-worker Patch
@rocket.chat/presence Patch
rocketchat-services Patch
@rocket.chat/models Patch
@rocket.chat/network-broker Patch
@rocket.chat/omni-core-ee Patch
@rocket.chat/instance-status Patch
@rocket.chat/omni-core Patch

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

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Sep 24, 2025

Walkthrough

Implements client-side validation preventing adding external users to non-federated rooms in both Create Channel modals, adds corresponding i18n strings, and bumps @rocket.chat/i18n and @rocket.chat/meteor patch versions.

Changes

Cohort / File(s) Summary
Create Channel validation (NavBar V2)
apps/meteor/client/NavBarV2/NavBarPagesGroup/actions/CreateChannelModal.tsx
Adds hasExternalMembers helper, validates Members field to block external users when room is not federated, and shows FieldError.
Create Channel validation (Sidebar header)
apps/meteor/client/sidebar/header/CreateChannel/CreateChannelModal.tsx
Adds hasExternalMembers helper, applies same validation and FieldError display without altering flow/structure.
Localization additions
packages/i18n/src/locales/en.i18n.json
Adds strings: __usersCount__people_will_be_invited, You_cannot_add_external_users_to_non_federated_room.
Changeset / dependencies
.changeset/rotten-dolphins-sort.md
Notes patch bumps for @rocket.chat/i18n and @rocket.chat/meteor and mentions new validation for external users on non-federated rooms.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User
  participant Modal as CreateChannelModal
  participant Validator as MembersValidation
  participant i18n as Translations

  User->>Modal: Enter members list
  Modal->>Validator: hasExternalMembers(members)
  alt Not federated AND hasExternalMembers
    Validator-->>Modal: Error key "You_cannot_add_external_users_to_non_federated_room"
    Modal->>i18n: t(errorKey)
    i18n-->>Modal: "You cannot add external users to a non-federated room"
    Modal-->>User: Show FieldError and disable submission
  else Federated OR no external members
    Validator-->>Modal: OK
    Modal-->>User: Allow submission
  end
Loading

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Suggested labels

stat: ready to merge, stat: QA assured

Suggested reviewers

  • ggazzo
  • ricardogarim

Poem

I twitch my whiskers, hop with glee,
New channels check who’s joining me.
If @-friends knock without a key,
“Federate first!” the forms decree.
With strings aligned in i18n,
We ship a tidy patch again. 🐇✨

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Linked Issues Check ⚠️ Warning The pull request introduces validation logic and displays an error when an external member is added to a non-federated channel, which addresses the core requirement of preventing such additions and provides user feedback. However, it does not disable the Create button as the linked issue’s quick fix specifies, and the federated toggle field remains in its original position contrary to the issue’s further improvement notes. Consequently, the quick fix requirement is only partially fulfilled and the federated field repositioning is still pending. Implement disabling of the Create button when external members are selected and consider moving the federated toggle field per the linked issue’s guidance to fully satisfy the quick fix and improvement objectives.
Out of Scope Changes Check ⚠️ Warning The pull request includes a changeset that bumps dependencies for @rocket.chat/i18n and @rocket.chat/meteor which are unrelated to the external member validation feature. These dependency updates are not required to implement the linked issue’s objectives and introduce out-of-scope modifications. Other alterations such as translation key additions and the validation logic itself directly support the intended fix. The dependency version bumps should be extracted into their own pull request to avoid scope creep and maintain focus on the validation feature, leaving this pull request to only contain changes directly related to the external member validation and translation updates.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The pull request title accurately describes the primary change of adding missing validation for external members and is concise without unnecessary detail. It clearly signals the purpose of the update to reviewers and aligns with the changeset focusing on validation logic. Therefore, it meets the title guidelines.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/create-channel-members-validation

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.

@juliajforesti juliajforesti added this to the 7.12.0 milestone Sep 24, 2025
@codecov
Copy link
Copy Markdown

codecov bot commented Sep 24, 2025

Codecov Report

❌ Patch coverage is 90.90909% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 67.32%. Comparing base (411f130) to head (1819393).
⚠️ Report is 9 commits behind head on develop.

Additional details and impacted files

Impacted file tree graph

@@             Coverage Diff             @@
##           develop   #37050      +/-   ##
===========================================
- Coverage    67.34%   67.32%   -0.02%     
===========================================
  Files         3339     3339              
  Lines       113178   113203      +25     
  Branches     20535    20533       -2     
===========================================
- Hits         76217    76212       -5     
- Misses       34355    34384      +29     
- Partials      2606     2607       +1     
Flag Coverage Δ
e2e 56.92% <33.33%> (-0.01%) ⬇️
unit 71.22% <90.90%> (-0.03%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@juliajforesti juliajforesti marked this pull request as ready for review September 24, 2025 16:38
@juliajforesti juliajforesti requested a review from a team as a code owner September 24, 2025 16:38
Copy link
Copy Markdown
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.

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)
apps/meteor/client/NavBarV2/NavBarPagesGroup/actions/CreateChannelModal.tsx (1)

366-377: Fix aria-describedby to point to an existing hint only when shown (Broadcast).

Currently the toggle always references ${broadcastId}-hint even when the hint isn’t rendered, which breaks a11y.

Apply this diff:

-                      <ToggleSwitch
-                        aria-describedby={`${broadcastId}-hint`}
-                        aria-labelledby={`${broadcastId}-label`}
+                      <ToggleSwitch
+                        aria-describedby={broadcast ? `${broadcastId}-hint` : undefined}
+                        aria-labelledby={`${broadcastId}-label`}
                         id={broadcastId}
                         ref={ref}
                         checked={value}
                         disabled={!!federated}
                         onChange={onChange}
                       />
🧹 Nitpick comments (6)
.changeset/rotten-dolphins-sort.md (1)

6-6: Tighten the changeset description.

Small grammar/style nit and clarify scope (both modals).

Apply this diff:

-Adds a validation to external users when creating a channel that is not federated
+Adds validation preventing adding external users to a non‑federated room in the Create Channel modals.
apps/meteor/client/NavBarV2/NavBarPagesGroup/actions/CreateChannelModal.tsx (2)

63-64: Make external-member detection more robust (Matrix ID format) and DRY.

Current check matches any string starting with '@' and may give false positives. Prefer a Matrix-like ID pattern and consider extracting to a shared util used by both modals.

Apply this diff:

-const hasExternalMembers = (members: string[]): boolean => members.some((member) => member.startsWith('@'));
+// External users are represented by Matrix-style IDs like "@user:domain"
+const hasExternalMembers = (members: string[]): boolean =>
+	members.some((member) => typeof member === 'string' && /^@[^:]+:.+$/i.test(member.trim()));

Optionally move this helper to a shared module and import it in both CreateChannelModal files to avoid duplication.


250-254: Wire up a11y attributes for Members errors.

Link the error to the input and expose invalid state.

Apply this diff:

-            render(({ field: { onChange, value } }): ReactElement => (
-              <UserAutoCompleteMultipleFederated id={addMembersId} value={value} onChange={onChange} placeholder={t('Add_people')} />
+            render(({ field: { onChange, value, ref } }): ReactElement => (
+              <UserAutoCompleteMultipleFederated
+                id={addMembersId}
+                ref={ref}
+                value={value}
+                onChange={onChange}
+                placeholder={t('Add_people')}
+                aria-invalid={errors.members ? 'true' : 'false'}
+                aria-describedby={errors.members ? `${addMembersId}-error` : undefined}
+              />
             )}
           />
apps/meteor/client/sidebar/header/CreateChannel/CreateChannelModal.tsx (2)

65-66: Make external-member detection more robust and share across modals.

Use a Matrix-style ID regex and avoid duplicating logic between files.

Apply this diff:

-const hasExternalMembers = (members: string[]): boolean => members.some((member) => member.startsWith('@'));
+const hasExternalMembers = (members: string[]): boolean =>
+	members.some((member) => typeof member === 'string' && /^@[^:]+:.+$/i.test(member.trim()));

Consider extracting this helper to a shared util.


254-258: Add a11y wiring for Members error state.

Connect error text to the input and set aria-invalid.

Apply this diff:

-            render(({ field: { onChange, value } }): ReactElement => (
-              <UserAutoCompleteMultipleFederated id={addMembersId} value={value} onChange={onChange} placeholder={t('Add_people')} />
+            render(({ field: { onChange, value, ref } }): ReactElement => (
+              <UserAutoCompleteMultipleFederated
+                id={addMembersId}
+                ref={ref}
+                value={value}
+                onChange={onChange}
+                placeholder={t('Add_people')}
+                aria-invalid={errors.members ? 'true' : 'false'}
+                aria-describedby={errors.members ? `${addMembersId}-error` : undefined}
+              />
             )}
           />
packages/i18n/src/locales/en.i18n.json (1)

7031-7031: Align terminology for non-federated room user errors The existing key error-cant-add-federated-users isn’t used in UI; choose one term—either keep “external users” (remove the unused translation key) or switch references in apps/meteor/client/sidebar/header/CreateChannel/CreateChannelModal.tsx and apps/meteor/client/NavBarV2/NavBarPagesGroup/actions/CreateChannelModal.tsx to error-cant-add-federated-users to maintain consistency.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between ddfb304 and 1819393.

📒 Files selected for processing (4)
  • .changeset/rotten-dolphins-sort.md (1 hunks)
  • apps/meteor/client/NavBarV2/NavBarPagesGroup/actions/CreateChannelModal.tsx (2 hunks)
  • apps/meteor/client/sidebar/header/CreateChannel/CreateChannelModal.tsx (2 hunks)
  • packages/i18n/src/locales/en.i18n.json (1 hunks)
🔇 Additional comments (3)
.changeset/rotten-dolphins-sort.md (1)

2-3: Confirm necessity of both package bumps.

Do we need to bump both @rocket.chat/i18n and @rocket.chat/meteor for this client-side change? If no server or package code changed that depends on these, consider limiting the bump to only what’s required.

apps/meteor/client/sidebar/header/CreateChannel/CreateChannelModal.tsx (1)

246-249: Server-side guard verification.

Client-side validation is good, but please confirm the API refuses external members on non-federated room creation to prevent bypass from other clients.

packages/i18n/src/locales/en.i18n.json (1)

7030-7030: Fix placeholder and add pluralization for correctness

  • Update en.i18n.json entry:
- "__usersCount__people_will_be_invited": "{{usersCount}} people will be invited"
+ "__usersCount__people_will_be_invited": {
+   "one": "{{count}} person will be invited",
+   "other": "{{count}} people will be invited"
+ }
  • Verify and update all call sites to pass count instead of usersCount.

Comment on lines +242 to +245
rules={{
validate: (members) =>
!federated && hasExternalMembers(members) ? t('You_cannot_add_external_users_to_non_federated_room') : true,
}}
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.

⚠️ Potential issue

Disable “Create” when invalid per FDR-161; revalidate on federated toggle for immediate feedback.

Validation alone blocks submit but doesn’t disable the button. Add a computed flag and re-trigger validation on federated changes.

Apply this diff:

   const {
     register,
     formState: { errors },
     handleSubmit,
     control,
     setValue,
     watch,
+    trigger,
   } = useForm({
     mode: 'onBlur',
     defaultValues: {
       members: [],
@@
   const { isPrivate, broadcast, readOnly, federated, encrypted } = watch();
+  const members = watch('members');
@@
   useEffect(() => {
     if (federated) {
       // if room is federated, it cannot be encrypted or broadcast or readOnly
       setValue('encrypted', false);
       setValue('broadcast', false);
       setValue('readOnly', false);
     }
   }, [federated, setValue]);
+
+  // Revalidate members when federated toggles so errors/hints update immediately
+  useEffect(() => {
+    void trigger('members');
+  }, [federated, trigger]);
@@
+  const shouldDisableCreate = !federated && hasExternalMembers(members);

And disable the Create button:

-          <Button type='submit' primary data-qa-type='create-channel-confirm-button'>
+          <Button type='submit' primary data-qa-type='create-channel-confirm-button' disabled={shouldDisableCreate}>
             {t('Create')}
           </Button>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
rules={{
validate: (members) =>
!federated && hasExternalMembers(members) ? t('You_cannot_add_external_users_to_non_federated_room') : true,
}}
// ── Inside CreateChannelModal.tsx ──
const {
register,
formState: { errors },
handleSubmit,
control,
setValue,
watch,
trigger,
} = useForm({
mode: 'onBlur',
defaultValues: {
members: [],
// …other defaults
},
});
const { isPrivate, broadcast, readOnly, federated, encrypted } = watch();
const members = watch('members');
useEffect(() => {
if (federated) {
// if room is federated, it cannot be encrypted or broadcast or readOnly
setValue('encrypted', false);
setValue('broadcast', false);
setValue('readOnly', false);
}
}, [federated, setValue]);
// Revalidate members when federated toggles so errors/hints update immediately
useEffect(() => {
void trigger('members');
}, [federated, trigger]);
const shouldDisableCreate = !federated && hasExternalMembers(members);
// …later in the JSX form
<Button
type='submit'
primary
data-qa-type='create-channel-confirm-button'
disabled={shouldDisableCreate}
>
{t('Create')}
</Button>
🤖 Prompt for AI Agents
In apps/meteor/client/NavBarV2/NavBarPagesGroup/actions/CreateChannelModal.tsx
around lines 242–245, the form validator prevents submission but the "Create"
button remains enabled and validation is not re-run when the "federated" toggle
changes; add a computed "isFormValid" (derived from react-hook-form's
formState.isValid or by running trigger()) and wire a useEffect to re-trigger
validation whenever the federated value changes, then pass isFormValid to
disable the Create button (disabled={!isFormValid}). Ensure trigger is awaited
or formState is subscribed to so the button updates immediately on federated
toggle.

Comment on lines +246 to +249
rules={{
validate: (members) =>
!federated && hasExternalMembers(members) ? t('You_cannot_add_external_users_to_non_federated_room') : true,
}}
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.

⚠️ Potential issue

Disable “Create” when invalid per FDR-161; revalidate on federated toggle.

Mirror the UX from the other modal: disable the submit button when external members are present and federated is off; revalidate on toggle.

Apply this diff:

   const {
     register,
     formState: { errors },
     handleSubmit,
     control,
     setValue,
     watch,
+    trigger,
   } = useForm({
     mode: 'onBlur',
@@
   const { isPrivate, broadcast, readOnly, federated, encrypted } = watch();
+  const members = watch('members');
@@
   useEffect(() => {
     if (federated) {
       // if room is federated, it cannot be encrypted or broadcast or readOnly
       setValue('encrypted', false);
       setValue('broadcast', false);
       setValue('readOnly', false);
     }
   }, [federated, setValue]);
+
+  useEffect(() => {
+    void trigger('members');
+  }, [federated, trigger]);
@@
+  const shouldDisableCreate = !federated && hasExternalMembers(members);

And disable the Create button:

-          <Button type='submit' primary data-qa-type='create-channel-confirm-button'>
+          <Button type='submit' primary data-qa-type='create-channel-confirm-button' disabled={shouldDisableCreate}>
             {t('Create')}
           </Button>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
rules={{
validate: (members) =>
!federated && hasExternalMembers(members) ? t('You_cannot_add_external_users_to_non_federated_room') : true,
}}
// …inside your CreateChannelModal component…
const {
register,
formState: { errors },
handleSubmit,
control,
setValue,
watch,
trigger,
} = useForm({
mode: 'onBlur',
// …other options…
});
const { isPrivate, broadcast, readOnly, federated, encrypted } = watch();
const members = watch('members');
useEffect(() => {
if (federated) {
// if room is federated, it cannot be encrypted or broadcast or readOnly
setValue('encrypted', false);
setValue('broadcast', false);
setValue('readOnly', false);
}
}, [federated, setValue]);
useEffect(() => {
// revalidate members whenever federated flips
void trigger('members');
}, [federated, trigger]);
const shouldDisableCreate = !federated && hasExternalMembers(members);
// …later, in your JSX…
<Button
type='submit'
primary
data-qa-type='create-channel-confirm-button'
disabled={shouldDisableCreate}
>
{t('Create')}
</Button>
🤖 Prompt for AI Agents
In apps/meteor/client/sidebar/header/CreateChannel/CreateChannelModal.tsx around
lines 246-249, the form validation already prevents adding external members when
federated is false, but the Create button isn't disabled and the rule isn't
re-run when the federated toggle changes; update the submit button to be
disabled when the form is invalid (e.g. disabled={!formState.isValid ||
formState.isSubmitting} or disabled={!watchFederated &&
hasExternalMembers(watch('members'))}) and, when the federated toggle changes,
call the form revalidation (e.g. await trigger(['members']) or trigger() after
updating the federated value) so the rule is re-evaluated and the button state
updates immediately.

Copy link
Copy Markdown
Contributor

@aleksandernsilva aleksandernsilva left a comment

Choose a reason for hiding this comment

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

Could we also add tests covering this scenario?

)}
/>
{errors.members && (
<FieldError aria-live='assertive' id={`${addMembersId}-error`}>
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.

I don't see this error id being referenced anywhere. I believe the auto complete is missing an aria-describedby.

)}
/>
{errors.members && (
<FieldError aria-live='assertive' id={`${addMembersId}-error`}>
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.

Same here.

@ggazzo ggazzo modified the milestones: 7.12.0, 7.11.0 Sep 24, 2025
@ggazzo ggazzo added the stat: QA assured Means it has been tested and approved by a company insider label Sep 24, 2025
@dionisio-bot dionisio-bot bot added the stat: ready to merge PR tested and approved waiting for merge label Sep 24, 2025
@ggazzo ggazzo merged commit 3cbb7da into develop Sep 24, 2025
54 checks passed
@ggazzo ggazzo deleted the fix/create-channel-members-validation branch September 24, 2025 21:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

stat: QA assured Means it has been tested and approved by a company insider stat: ready to merge PR tested and approved waiting for merge

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants