diff --git a/src/gateway/server-http.ts b/src/gateway/server-http.ts index c6c0cabd1ff..8f605caae43 100644 --- a/src/gateway/server-http.ts +++ b/src/gateway/server-http.ts @@ -86,6 +86,54 @@ const GATEWAY_PROBE_STATUS_BY_PATH = new Map([ ]); const MATTERMOST_SLASH_CALLBACK_PATH = "/api/channels/mattermost/command"; +function resolveMattermostSlashCallbackPaths( + configSnapshot: ReturnType, +): Set { + const callbackPaths = new Set([MATTERMOST_SLASH_CALLBACK_PATH]); + + const normalizeCallbackPath = (value: unknown): string => { + const trimmed = typeof value === "string" ? value.trim() : ""; + if (!trimmed) { + return MATTERMOST_SLASH_CALLBACK_PATH; + } + return trimmed.startsWith("/") ? trimmed : `/${trimmed}`; + }; + + const tryAddCallbackUrlPath = (rawUrl: unknown) => { + if (typeof rawUrl !== "string") { + return; + } + const trimmed = rawUrl.trim(); + if (!trimmed) { + return; + } + try { + const pathname = new URL(trimmed).pathname; + if (pathname) { + callbackPaths.add(pathname); + } + } catch { + // Ignore invalid callback URLs in config and keep default path behavior. + } + }; + + const mmRaw = configSnapshot.channels?.mattermost as Record | undefined; + const addMmCommands = (raw: unknown) => { + const commands = raw as Record | undefined; + callbackPaths.add(normalizeCallbackPath(commands?.callbackPath)); + tryAddCallbackUrlPath(commands?.callbackUrl); + }; + + addMmCommands(mmRaw?.commands); + const accountsRaw = (mmRaw?.accounts ?? {}) as Record; + for (const accountId of Object.keys(accountsRaw)) { + const accountCfg = accountsRaw[accountId] as Record | undefined; + addMmCommands(accountCfg?.commands); + } + + return callbackPaths; +} + function shouldEnforceDefaultPluginGatewayAuth(pathContext: PluginRoutePathContext): boolean { return ( pathContext.malformedEncoding || @@ -175,6 +223,7 @@ function buildPluginRequestStages(params: { req: IncomingMessage; res: ServerResponse; requestPath: string; + mattermostSlashCallbackPaths: ReadonlySet; pluginPathContext: PluginRoutePathContext | null; handlePluginRequest?: PluginHttpRequestHandler; shouldEnforcePluginGatewayAuth?: (pathContext: PluginRoutePathContext) => boolean; @@ -190,7 +239,7 @@ function buildPluginRequestStages(params: { { name: "plugin-auth", run: async () => { - if (params.requestPath === MATTERMOST_SLASH_CALLBACK_PATH) { + if (params.mattermostSlashCallbackPaths.has(params.requestPath)) { return false; } const pathContext = @@ -510,6 +559,7 @@ export function createGatewayHttpServer(opts: { req.url = scopedCanvas.rewrittenUrl; } const requestPath = new URL(req.url ?? "/", "http://localhost").pathname; + const mattermostSlashCallbackPaths = resolveMattermostSlashCallbackPaths(configSnapshot); const pluginPathContext = handlePluginRequest ? resolvePluginRoutePathContext(requestPath) : null; @@ -599,6 +649,7 @@ export function createGatewayHttpServer(opts: { req, res, requestPath, + mattermostSlashCallbackPaths, pluginPathContext, handlePluginRequest, shouldEnforcePluginGatewayAuth,