Tests: fast-path gateway auth bypass discovery

This commit is contained in:
Gustavo Madeira Santana
2026-04-17 01:57:42 -04:00
parent 178c36532d
commit 807c6648f9
8 changed files with 229 additions and 82 deletions

View File

@@ -0,0 +1 @@
export { resolveMattermostGatewayAuthBypassPaths as resolveGatewayAuthBypassPaths } from "./src/gateway-auth-bypass.js";

View File

@@ -5,14 +5,16 @@ import {
createScopedChannelConfigAdapter,
} from "openclaw/plugin-sdk/channel-config-helpers";
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
import {
collectMattermostSlashCallbackPaths,
resolveMattermostGatewayAuthBypassPaths,
} from "./gateway-auth-bypass.js";
import {
listMattermostAccountIds,
resolveDefaultMattermostAccountId,
resolveMattermostAccount,
type ResolvedMattermostAccount,
} from "./mattermost/accounts.js";
import type { MattermostSlashCommandConfig } from "./mattermost/slash-commands.js";
import type { MattermostConfig } from "./types.js";
export const mattermostMeta = {
id: "mattermost",
@@ -27,8 +29,6 @@ export const mattermostMeta = {
quickstartAllowFrom: true,
} as const;
const DEFAULT_SLASH_CALLBACK_PATH = "/api/channels/mattermost/command";
export function normalizeMattermostAllowEntry(entry: string): string {
return normalizeLowercaseStringOrEmpty(
entry
@@ -50,62 +50,7 @@ export function formatMattermostAllowEntry(entry: string): string {
return normalizeLowercaseStringOrEmpty(trimmed.replace(/^(mattermost|user):/i, ""));
}
export function collectMattermostSlashCallbackPaths(
raw?: Partial<MattermostSlashCommandConfig>,
): string[] {
const callbackPath = (() => {
const trimmed = raw?.callbackPath?.trim();
if (!trimmed) {
return DEFAULT_SLASH_CALLBACK_PATH;
}
return trimmed.startsWith("/") ? trimmed : `/${trimmed}`;
})();
const callbackUrl = raw?.callbackUrl?.trim();
const paths = new Set<string>([callbackPath]);
if (callbackUrl) {
try {
const pathname = new URL(callbackUrl).pathname;
if (pathname) {
paths.add(pathname);
}
} catch {
// Keep the normalized callback path when the configured URL is invalid.
}
}
return [...paths];
}
export function resolveMattermostGatewayAuthBypassPaths(cfg: {
channels?: Record<string, unknown>;
}): string[] {
const base = cfg.channels?.mattermost as MattermostConfig | undefined;
const callbackPaths = new Set(
collectMattermostSlashCallbackPaths(
base?.commands as Partial<MattermostSlashCommandConfig> | undefined,
).filter(
(path) =>
path === "/api/channels/mattermost/command" || path.startsWith("/api/channels/mattermost/"),
),
);
const accounts = base?.accounts ?? {};
for (const account of Object.values(accounts)) {
const accountConfig =
account && typeof account === "object" && !Array.isArray(account)
? (account as {
commands?: Parameters<typeof collectMattermostSlashCallbackPaths>[0];
})
: undefined;
for (const path of collectMattermostSlashCallbackPaths(accountConfig?.commands)) {
if (
path === "/api/channels/mattermost/command" ||
path.startsWith("/api/channels/mattermost/")
) {
callbackPaths.add(path);
}
}
}
return [...callbackPaths];
}
export { collectMattermostSlashCallbackPaths, resolveMattermostGatewayAuthBypassPaths };
export const mattermostConfigAdapter = createScopedChannelConfigAdapter<ResolvedMattermostAccount>({
sectionKey: "mattermost",

View File

@@ -0,0 +1,38 @@
import { describe, expect, it } from "vitest";
import {
collectMattermostSlashCallbackPaths,
resolveMattermostGatewayAuthBypassPaths,
} from "./gateway-auth-bypass.js";
describe("Mattermost gateway auth bypass paths", () => {
it("normalizes slash callback paths and callback URL paths", () => {
expect(
collectMattermostSlashCallbackPaths({
callbackPath: "api/channels/mattermost/command",
callbackUrl: "https://gateway.example.com/api/channels/mattermost/custom",
}),
).toEqual(["/api/channels/mattermost/command", "/api/channels/mattermost/custom"]);
});
it("keeps only Mattermost channel callback paths", () => {
expect(
resolveMattermostGatewayAuthBypassPaths({
channels: {
mattermost: {
commands: {
callbackPath: "/api/channels/mattermost/command",
callbackUrl: "https://gateway.example.com/api/channels/nostr/default/profile",
},
accounts: {
work: {
commands: {
callbackPath: "/api/channels/mattermost/work",
},
},
},
},
},
}),
).toEqual(["/api/channels/mattermost/command", "/api/channels/mattermost/work"]);
});
});

View File

@@ -0,0 +1,83 @@
const DEFAULT_SLASH_CALLBACK_PATH = "/api/channels/mattermost/command";
type MattermostSlashCommandConfigInput = {
callbackPath?: unknown;
callbackUrl?: unknown;
};
type MattermostAccountConfigInput = {
commands?: MattermostSlashCommandConfigInput;
};
type MattermostConfigInput = MattermostAccountConfigInput & {
accounts?: Record<string, unknown>;
};
function readTrimmedString(value: unknown): string | undefined {
return typeof value === "string" && value.trim() ? value.trim() : undefined;
}
function normalizeCallbackPath(value: unknown): string {
const trimmed = readTrimmedString(value);
if (!trimmed) {
return DEFAULT_SLASH_CALLBACK_PATH;
}
return trimmed.startsWith("/") ? trimmed : `/${trimmed}`;
}
function readMattermostCommands(value: unknown): MattermostSlashCommandConfigInput | undefined {
return value && typeof value === "object" && !Array.isArray(value)
? (value as MattermostSlashCommandConfigInput)
: undefined;
}
function isMattermostBypassPath(path: string): boolean {
return path === DEFAULT_SLASH_CALLBACK_PATH || path.startsWith("/api/channels/mattermost/");
}
export function collectMattermostSlashCallbackPaths(
raw?: MattermostSlashCommandConfigInput,
): string[] {
const paths = new Set<string>([normalizeCallbackPath(raw?.callbackPath)]);
const callbackUrl = readTrimmedString(raw?.callbackUrl);
if (callbackUrl) {
try {
const pathname = new URL(callbackUrl).pathname;
if (pathname) {
paths.add(pathname);
}
} catch {
// Keep the normalized callback path when the configured URL is invalid.
}
}
return [...paths];
}
export function resolveMattermostGatewayAuthBypassPaths(cfg: {
channels?: Record<string, unknown>;
}): string[] {
const base =
cfg.channels?.mattermost && typeof cfg.channels.mattermost === "object"
? (cfg.channels.mattermost as MattermostConfigInput)
: undefined;
const callbackPaths = new Set(
collectMattermostSlashCallbackPaths(readMattermostCommands(base?.commands)).filter(
isMattermostBypassPath,
),
);
const accounts = base?.accounts ?? {};
for (const account of Object.values(accounts)) {
const accountConfig =
account && typeof account === "object" && !Array.isArray(account)
? (account as MattermostAccountConfigInput)
: undefined;
for (const path of collectMattermostSlashCallbackPaths(
readMattermostCommands(accountConfig?.commands),
)) {
if (isMattermostBypassPath(path)) {
callbackPaths.add(path);
}
}
}
return [...callbackPaths];
}