feat: handle command injection optionally#1548
Conversation
|
Size Change: +1.91 kB (+0.44%) Total Size: 432 kB
|
…dle-command-injection
…dle-command-injection
| }, | ||
| }); | ||
|
|
||
| export const isTextMatched = (input: string, command: string): boolean => { |
There was a problem hiding this comment.
Isn't this a duplicate logic already present in another util function in this file?
There was a problem hiding this comment.
Which utility are you talking about here?
src/messageComposer/middleware/textComposer/applyCommandSettings.ts
Outdated
Show resolved
Hide resolved
src/messageComposer/middleware/textComposer/applyCommandSettings.ts
Outdated
Show resolved
Hide resolved
src/messageComposer/middleware/textComposer/applyCommandSettings.ts
Outdated
Show resolved
Hide resolved
src/messageComposer/middleware/textComposer/applyCommandSettings.ts
Outdated
Show resolved
Hide resolved
src/messageComposer/middleware/messageComposer/commandInjection.ts
Outdated
Show resolved
Hide resolved
src/messageComposer/middleware/textComposer/applyCommandSettings.ts
Outdated
Show resolved
Hide resolved
|
I think this PR should also solve the scenario, when a user wants to cancel the command immediately after its selection - for example by pressing backspace - when there is no change event, but rather keyDown event. |
src/messageComposer/middleware/textComposer/commandStringExtraction.ts
Outdated
Show resolved
Hide resolved
src/messageComposer/middleware/textComposer/commandStringExtraction.ts
Outdated
Show resolved
Hide resolved
…dle-command-injection
|
|
||
| const commands = searchSource?.query(searchQuery).items; | ||
|
|
||
| const matchedCommand = commands?.find((command) => |
There was a problem hiding this comment.
@khushal87 does it make sense to perform query(searchQuery) and then again commands.find()? Isn't it doing the same with the difference that commands.find in this case would just be commands[0] (as find returns on the first matched and the first matched is the at 0 index before query returns all the matches)?
There was a problem hiding this comment.
I will simplify it. Makes sense
|
|
||
| const query = triggerWithToken.slice(1); | ||
|
|
||
| const searchQuery = getFirstWordFromText(query); |
There was a problem hiding this comment.
Why is it necessary to get first word from query if the query itself is just a trigger followed by chars that are not white space?
There was a problem hiding this comment.
Now, if you allow spaces, the user can paste anything let's say /giphy good morning, in that case,e the query will be giphy good morning but the search source cannot return anything as the pattern didn't match. That is why we get the first word from the query just for the command search.
| new RegExp( | ||
| isCommand | ||
| ? `^[${trigger}]${triggerNorWhitespace}$` | ||
| ? `^[${trigger}]${notTrigger}$` |
There was a problem hiding this comment.
why do we allow white space?
There was a problem hiding this comment.
The problem is the function returns null for any text except the pattern /command which, we cannot rely on if we want to use it as an input for search later in the middleware. That is why I allowed it.
| * @param textToBeMatchedWith | ||
| * @returns | ||
| */ | ||
| export const startsWithTextAndSpace = ( |
There was a problem hiding this comment.
I think we may not need this function. It is used in a single place and it actually removes white space that was kept previously due to allowing white space in getTriggerWithToken
There was a problem hiding this comment.
Well, this function just checks if the query starts with the command name or not, and we are obviously allowing at least one or more spaces so that only then it would return true for a match, i.e. /unban will be a matched command and /unban would not for the onChange.
| * @param text - The input text from which to extract the first word. | ||
| * @returns The first word found in the text, or an empty string if no word is found. | ||
| */ | ||
| export const getFirstWordFromText = (text: string): string => { |
There was a problem hiding this comment.
Also this function exists only because we introduced this change:
https://github.com/GetStream/stream-chat-js/pull/1548/files#diff-3f1a5dd2e123452ae2ff28a98798ca79d3cf5fde3c8f1c82d3e406c95d7967e5R26
| * @param trigger - The trigger string to be removed from the start of the text. | ||
| * @returns The text with the trigger removed from the start. | ||
| */ | ||
| export const stripTriggerFromText = (text: string, trigger: string) => |
There was a problem hiding this comment.
This does not strip trigger. In the single place where it is used it strips a trigger with command name. Trigger is for example "/" but not "/ban".
The function itself could also be renamed to stripTextFromText
There was a problem hiding this comment.
How about stripTextFromStartOfTheText as it strips the text from the start of the text?
src/messageComposer/textComposer.ts
Outdated
| }; | ||
|
|
||
| setCommand = (command: CommandResponse | null) => { | ||
| if (command === this.command) return; |
There was a problem hiding this comment.
This may not work if the objects have not the same memory reference, but their content refer to the same command. Maybe something like command.name === this.command.name would be more appropriate or to have a function that evaluates that two command objects are equal.
There was a problem hiding this comment.
Fixed using the 1st way
## [9.8.0](v9.7.0...v9.8.0) (2025-06-19) ### Bug Fixes * allow to nullify reminder's remind_at value ([#1569](#1569)) ([c1c6cf4](c1c6cf4)) * propagate stopTimerRefreshBoundaryMs to all reminder timers on ReminderManager config update ([#1568](#1568)) ([944d1c2](944d1c2)) * run onChange middleware on TextComposer.insertText ([#1570](#1570)) ([3768007](3768007)) ### Features * handle command injection optionally ([#1548](#1548)) ([ea912f9](ea912f9)) ### Refactors * protect ReminderPaginator filters and sort properties ([#1567](#1567)) ([1a3f4c4](1a3f4c4))
|
🎉 This PR is included in version 9.8.0 🎉 The release is available on: Your semantic-release bot 📦🚀 |
The goal of the PR is to handle command injection and apply the command settings to the text using middleware that the SDK or apps can plug in. The existing commands middleware will be responsible for setting the
commandstate to thetextComposer, which can be used by the other middleware.SDKs can basically add something like this to support the commands UI on message input: