@@ -10,6 +10,7 @@ import type { OpenClawConfig, ReplyPayload, RuntimeEnv } from "openclaw/plugin-s
1010import {
1111 createReplyPrefixOptions ,
1212 createTypingCallbacks ,
13+ isDangerousNameMatchingEnabled ,
1314 logTypingFailure ,
1415 resolveControlCommandGate ,
1516} from "openclaw/plugin-sdk" ;
@@ -23,6 +24,11 @@ import {
2324 sendMattermostTyping ,
2425 type MattermostChannel ,
2526} from "./client.js" ;
27+ import {
28+ isMattermostSenderAllowed ,
29+ normalizeMattermostAllowList ,
30+ resolveMattermostEffectiveAllowFromLists ,
31+ } from "./monitor-auth.js" ;
2632import { sendMessageMattermost } from "./send.js" ;
2733import {
2834 parseSlashCommandPayload ,
@@ -72,46 +78,6 @@ function sendJsonResponse(
7278 res . end ( JSON . stringify ( body ) ) ;
7379}
7480
75- /**
76- * Normalize a single allowlist entry, matching the websocket monitor behaviour.
77- * Strips `mattermost:`, `user:`, and `@` prefixes, and preserves the `*` wildcard.
78- */
79- function normalizeAllowEntry ( entry : string ) : string {
80- const trimmed = entry . trim ( ) ;
81- if ( ! trimmed ) {
82- return "" ;
83- }
84- if ( trimmed === "*" ) {
85- return "*" ;
86- }
87- return trimmed
88- . replace ( / ^ ( m a t t e r m o s t | u s e r ) : / i, "" )
89- . replace ( / ^ @ / , "" )
90- . toLowerCase ( ) ;
91- }
92-
93- function normalizeAllowList ( entries : Array < string | number > ) : string [ ] {
94- const normalized = entries . map ( ( entry ) => normalizeAllowEntry ( String ( entry ) ) ) . filter ( Boolean ) ;
95- return Array . from ( new Set ( normalized ) ) ;
96- }
97-
98- function isSenderAllowed ( params : { senderId : string ; senderName : string ; allowFrom : string [ ] } ) {
99- const { senderId, senderName, allowFrom } = params ;
100- if ( allowFrom . length === 0 ) {
101- return false ;
102- }
103- if ( allowFrom . includes ( "*" ) ) {
104- return true ;
105- }
106-
107- const normalizedId = normalizeAllowEntry ( senderId ) ;
108- const normalizedName = senderName ? normalizeAllowEntry ( senderName ) : "" ;
109-
110- return allowFrom . some (
111- ( entry ) => entry === normalizedId || ( normalizedName && entry === normalizedName ) ,
112- ) ;
113- }
114-
11581type SlashInvocationAuth = {
11682 ok : boolean ;
11783 denyResponse ?: MattermostSlashCommandResponse ;
@@ -181,49 +147,58 @@ async function authorizeSlashInvocation(params: {
181147 const dmPolicy = account . config . dmPolicy ?? "pairing" ;
182148 const defaultGroupPolicy = cfg . channels ?. defaults ?. groupPolicy ;
183149 const groupPolicy = account . config . groupPolicy ?? defaultGroupPolicy ?? "allowlist" ;
150+ const allowNameMatching = isDangerousNameMatchingEnabled ( account . config ) ;
184151
185- const configAllowFrom = normalizeAllowList ( account . config . allowFrom ?? [ ] ) ;
186- const configGroupAllowFrom = normalizeAllowList ( account . config . groupAllowFrom ?? [ ] ) ;
187- const storeAllowFrom = normalizeAllowList (
152+ const configAllowFrom = normalizeMattermostAllowList ( account . config . allowFrom ?? [ ] ) ;
153+ const configGroupAllowFrom = normalizeMattermostAllowList ( account . config . groupAllowFrom ?? [ ] ) ;
154+ const storeAllowFrom = normalizeMattermostAllowList (
188155 await core . channel . pairing
189156 . readAllowFromStore ( {
190157 channel : "mattermost" ,
191158 accountId : account . accountId ,
192159 } )
193160 . catch ( ( ) => [ ] ) ,
194161 ) ;
195- const effectiveAllowFrom = Array . from ( new Set ( [ ...configAllowFrom , ...storeAllowFrom ] ) ) ;
196- const effectiveGroupAllowFrom = Array . from (
197- new Set ( [
198- ...( configGroupAllowFrom . length > 0 ? configGroupAllowFrom : configAllowFrom ) ,
199- ...storeAllowFrom ,
200- ] ) ,
201- ) ;
162+ const { effectiveAllowFrom, effectiveGroupAllowFrom } = resolveMattermostEffectiveAllowFromLists ( {
163+ allowFrom : configAllowFrom ,
164+ groupAllowFrom : configGroupAllowFrom ,
165+ storeAllowFrom,
166+ dmPolicy,
167+ } ) ;
202168
203169 const allowTextCommands = core . channel . commands . shouldHandleTextCommands ( {
204170 cfg,
205171 surface : "mattermost" ,
206172 } ) ;
207173 const hasControlCommand = core . channel . text . hasControlCommand ( commandText , cfg ) ;
208174 const useAccessGroups = cfg . commands ?. useAccessGroups !== false ;
175+ const commandDmAllowFrom = kind === "direct" ? effectiveAllowFrom : configAllowFrom ;
176+ const commandGroupAllowFrom =
177+ kind === "direct"
178+ ? effectiveGroupAllowFrom
179+ : configGroupAllowFrom . length > 0
180+ ? configGroupAllowFrom
181+ : configAllowFrom ;
209182
210- const senderAllowedForCommands = isSenderAllowed ( {
183+ const senderAllowedForCommands = isMattermostSenderAllowed ( {
211184 senderId,
212185 senderName,
213- allowFrom : effectiveAllowFrom ,
186+ allowFrom : commandDmAllowFrom ,
187+ allowNameMatching,
214188 } ) ;
215- const groupAllowedForCommands = isSenderAllowed ( {
189+ const groupAllowedForCommands = isMattermostSenderAllowed ( {
216190 senderId,
217191 senderName,
218- allowFrom : effectiveGroupAllowFrom ,
192+ allowFrom : commandGroupAllowFrom ,
193+ allowNameMatching,
219194 } ) ;
220195
221196 const commandGate = resolveControlCommandGate ( {
222197 useAccessGroups,
223198 authorizers : [
224- { configured : effectiveAllowFrom . length > 0 , allowed : senderAllowedForCommands } ,
199+ { configured : commandDmAllowFrom . length > 0 , allowed : senderAllowedForCommands } ,
225200 {
226- configured : effectiveGroupAllowFrom . length > 0 ,
201+ configured : commandGroupAllowFrom . length > 0 ,
227202 allowed : groupAllowedForCommands ,
228203 } ,
229204 ] ,
@@ -661,16 +636,22 @@ async function handleSlashCommandAsync(params: {
661636 onReplyStart : typingCallbacks . onReplyStart ,
662637 } ) ;
663638
664- await core . channel . reply . dispatchReplyFromConfig ( {
665- ctx : ctxPayload ,
666- cfg,
639+ await core . channel . reply . withReplyDispatcher ( {
667640 dispatcher,
668- replyOptions : {
669- ...replyOptions ,
670- disableBlockStreaming :
671- typeof account . blockStreaming === "boolean" ? ! account . blockStreaming : undefined ,
672- onModelSelected,
641+ onSettled : ( ) => {
642+ markDispatchIdle ( ) ;
673643 } ,
644+ run : ( ) =>
645+ core . channel . reply . dispatchReplyFromConfig ( {
646+ ctx : ctxPayload ,
647+ cfg,
648+ dispatcher,
649+ replyOptions : {
650+ ...replyOptions ,
651+ disableBlockStreaming :
652+ typeof account . blockStreaming === "boolean" ? ! account . blockStreaming : undefined ,
653+ onModelSelected,
654+ } ,
655+ } ) ,
674656 } ) ;
675- markDispatchIdle ( ) ;
676657}
0 commit comments