-
Notifications
You must be signed in to change notification settings - Fork 13.1k
regression(federation): allow invite users with upper case username #37970
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
54 changes: 54 additions & 0 deletions
54
ee/packages/federation-matrix/src/helpers/createOrUpdateFederatedUser.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,54 @@ | ||
| import { type IUser, UserStatus } from '@rocket.chat/core-typings'; | ||
| import { Users } from '@rocket.chat/models'; | ||
|
|
||
| /** | ||
| * Helper function to create a federated user | ||
| * | ||
| * Because of historical reasons, we can have users only with federated flag but no federation object | ||
| * So we need to upsert the user with the federation object | ||
| */ | ||
|
|
||
| export async function createOrUpdateFederatedUser(options: { username: string; name?: string; origin: string }): Promise<IUser> { | ||
| const { username, name = username, origin } = options; | ||
|
|
||
| console.log('createOrUpdateFederatedUser ->', options); | ||
|
|
||
| // TODO: Have a specific method to handle this upsert | ||
| const user = await Users.findOneAndUpdate( | ||
| { | ||
| username, | ||
| }, | ||
| { | ||
| $set: { | ||
| username, | ||
| name: name || username, | ||
| type: 'user' as const, | ||
| status: UserStatus.OFFLINE, | ||
| active: true, | ||
| roles: ['user'], | ||
| requirePasswordChange: false, | ||
| federated: true, | ||
| federation: { | ||
| version: 1, | ||
| mui: username, | ||
| origin, | ||
| }, | ||
| _updatedAt: new Date(), | ||
| }, | ||
| $setOnInsert: { | ||
| createdAt: new Date(), | ||
| }, | ||
| }, | ||
| { | ||
| upsert: true, | ||
| projection: { _id: 1, username: 1 }, | ||
| returnDocument: 'after', | ||
| }, | ||
| ); | ||
|
|
||
| if (!user) { | ||
| throw new Error(`Failed to create or update federated user: ${username}`); | ||
| } | ||
|
|
||
| return user; | ||
| } | ||
7 changes: 7 additions & 0 deletions
7
ee/packages/federation-matrix/src/helpers/extractDomainFromMatrixUserId.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| export const extractDomainFromMatrixUserId = (mxid: string): string => { | ||
| const separatorIndex = mxid.indexOf(':', 1); | ||
| if (separatorIndex === -1) { | ||
| throw new Error(`Invalid federated username: ${mxid}`); | ||
| } | ||
| return mxid.substring(separatorIndex + 1); | ||
| }; |
21 changes: 21 additions & 0 deletions
21
ee/packages/federation-matrix/src/helpers/getUsernameServername.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| import { extractDomainFromMatrixUserId } from './extractDomainFromMatrixUserId'; | ||
|
|
||
| /** | ||
| * Extract the username and the servername from a matrix user id | ||
| * if the serverName is the same as the serverName in the mxid, return only the username (rocket.chat regular username) | ||
| * otherwise, return the full mxid and the servername | ||
| */ | ||
|
|
||
| export const getUsernameServername = (mxid: string, serverName: string): [mxid: string, serverName: string, isLocal: boolean] => { | ||
| const senderServerName = extractDomainFromMatrixUserId(mxid); | ||
| // if the serverName is the same as the serverName in the mxid, return only the username (rocket.chat regular username) | ||
| if (serverName === senderServerName) { | ||
| const separatorIndex = mxid.indexOf(':', 1); | ||
| if (separatorIndex === -1) { | ||
| throw new Error(`Invalid federated username: ${mxid}`); | ||
| } | ||
| return [mxid.substring(1, separatorIndex), senderServerName, true]; // removers also the @ | ||
| } | ||
|
|
||
| return [mxid, senderServerName, false]; | ||
| }; |
94 changes: 94 additions & 0 deletions
94
ee/packages/federation-matrix/src/helpers/validateFederatedUsername.spec.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,94 @@ | ||
| import { validateFederatedUsername } from './validateFederatedUsername'; | ||
|
|
||
| describe('validateFederatedUsername', () => { | ||
| describe('invalid formats', () => { | ||
| it('should return false when mxid does not start with @', () => { | ||
| expect(validateFederatedUsername('user:example.com')).toBe(false); | ||
| }); | ||
|
|
||
| it('should return false when mxid has no colon separator', () => { | ||
| expect(validateFederatedUsername('@user')).toBe(false); | ||
| }); | ||
|
|
||
| it('should return false when mxid has empty localpart', () => { | ||
| expect(validateFederatedUsername('@:example.com')).toBe(false); | ||
| }); | ||
|
|
||
| it('should return false when localpart contains invalid characters', () => { | ||
| expect(validateFederatedUsername('@user@name:example.com')).toBe(false); | ||
| expect(validateFederatedUsername('@user#name:example.com')).toBe(false); | ||
| }); | ||
|
|
||
| it('should return false when localpart exceeds 255 characters', () => { | ||
| const longLocalpart = 'a'.repeat(256); | ||
| expect(validateFederatedUsername(`@${longLocalpart}:example.com`)).toBe(false); | ||
| }); | ||
|
|
||
| it('should return false when domain is invalid', () => { | ||
| expect(validateFederatedUsername('@user:invalid_domain')).toBe(false); | ||
| expect(validateFederatedUsername('@user:-example.com')).toBe(false); | ||
| }); | ||
|
|
||
| it('should return false when port is invalid', () => { | ||
| expect(validateFederatedUsername('@user:example.com:0')).toBe(false); | ||
| expect(validateFederatedUsername('@user:example.com:65536')).toBe(false); | ||
| expect(validateFederatedUsername('@user:example.com:abc')).toBe(false); | ||
| expect(validateFederatedUsername('@user:example.com:-1')).toBe(false); | ||
| }); | ||
| }); | ||
|
|
||
| describe('valid formats', () => { | ||
| it('should return true for basic valid mxid', () => { | ||
| expect(validateFederatedUsername('@user:example.com')).toBe(true); | ||
| }); | ||
|
|
||
| it('should return true when localpart contains uppercase letters', () => { | ||
| expect(validateFederatedUsername('@User:example.com')).toBe(true); | ||
| }); | ||
|
|
||
| it('should return true for mxid with dots and hyphens in localpart', () => { | ||
| expect(validateFederatedUsername('@user.name:example.com')).toBe(true); | ||
| expect(validateFederatedUsername('@user-name:example.com')).toBe(true); | ||
| expect(validateFederatedUsername('@user_name:example.com')).toBe(true); | ||
| }); | ||
|
|
||
| it('should return true for mxid with encoded characters in localpart', () => { | ||
| expect(validateFederatedUsername('@user=2dname:example.com')).toBe(true); | ||
| expect(validateFederatedUsername('@user=2Dname:example.com')).toBe(true); | ||
| }); | ||
|
|
||
| it('should return true for mxid with subdomain', () => { | ||
| expect(validateFederatedUsername('@user:subdomain.example.com')).toBe(true); | ||
| }); | ||
|
|
||
| it('should return true for mxid with valid port', () => { | ||
| expect(validateFederatedUsername('@user:example.com:8008')).toBe(true); | ||
| expect(validateFederatedUsername('@user:example.com:1')).toBe(true); | ||
| expect(validateFederatedUsername('@user:example.com:65535')).toBe(true); | ||
| }); | ||
|
|
||
| it('should return true for mxid with IPv4 address', () => { | ||
| expect(validateFederatedUsername('@user:192.168.1.1')).toBe(true); | ||
| expect(validateFederatedUsername('@user:192.168.1.1:8008')).toBe(true); | ||
| }); | ||
|
|
||
| it('should return true for mxid with IPv6 address', () => { | ||
| expect(validateFederatedUsername('@user:[::1]')).toBe(true); | ||
| expect(validateFederatedUsername('@user:[2001:db8::1]')).toBe(true); | ||
| }); | ||
|
|
||
| it('should return true for mxid with numbers in localpart', () => { | ||
| expect(validateFederatedUsername('@user123:example.com')).toBe(true); | ||
| expect(validateFederatedUsername('@123user:example.com')).toBe(true); | ||
| }); | ||
|
|
||
| it('should return true for mxid with single character localpart', () => { | ||
| expect(validateFederatedUsername('@a:example.com')).toBe(true); | ||
| }); | ||
|
|
||
| it('should return true for mxid with 255 character localpart', () => { | ||
| const maxLocalpart = 'a'.repeat(255); | ||
| expect(validateFederatedUsername(`@${maxLocalpart}:example.com`)).toBe(true); | ||
| }); | ||
| }); | ||
| }); |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove debug console.log statement.
The console.log statement should be removed before merging to production. Use a proper logger if debugging information is needed.
🔎 Proposed fix
📝 Committable suggestion
🤖 Prompt for AI Agents