|
6 | 6 | */ |
7 | 7 |
|
8 | 8 | import type { IncomingMessage, ServerResponse } from "node:http"; |
| 9 | +import { safeEqualSecret } from "openclaw/plugin-sdk/browser-support"; |
9 | 10 | import type { ResolvedMattermostAccount } from "../mattermost/accounts.js"; |
10 | 11 | import { getMattermostRuntime } from "../runtime.js"; |
11 | 12 | import { |
@@ -78,6 +79,18 @@ function sendJsonResponse( |
78 | 79 | res.end(JSON.stringify(body)); |
79 | 80 | } |
80 | 81 |
|
| 82 | +function matchesRegisteredCommandToken( |
| 83 | + commandTokens: ReadonlySet<string>, |
| 84 | + candidate: string, |
| 85 | +): boolean { |
| 86 | + for (const token of commandTokens) { |
| 87 | + if (safeEqualSecret(candidate, token)) { |
| 88 | + return true; |
| 89 | + } |
| 90 | + } |
| 91 | + return false; |
| 92 | +} |
| 93 | + |
81 | 94 | type SlashInvocationAuth = { |
82 | 95 | ok: boolean; |
83 | 96 | denyResponse?: MattermostSlashCommandResponse; |
@@ -242,7 +255,7 @@ export function createSlashCommandHttpHandler(params: SlashHttpHandlerParams) { |
242 | 255 |
|
243 | 256 | // Validate token — fail closed: reject when no tokens are registered |
244 | 257 | // (e.g. registration failed or startup was partial) |
245 | | - if (commandTokens.size === 0 || !commandTokens.has(payload.token)) { |
| 258 | + if (commandTokens.size === 0 || !matchesRegisteredCommandToken(commandTokens, payload.token)) { |
246 | 259 | sendJsonResponse(res, 401, { |
247 | 260 | response_type: "ephemeral", |
248 | 261 | text: "Unauthorized: invalid command token.", |
|
0 commit comments