fix(outbound): return error instead of silently redirecting to allowList[0] (#13578)

This commit is contained in:
Marcus Castro
2026-02-13 01:20:03 -03:00
committed by GitHub
parent a43136c85e
commit 39ee708df6
11 changed files with 392 additions and 74 deletions

View File

@@ -108,15 +108,15 @@ describe("outbound", () => {
expect(result.to).toBe("allowed");
});
it("should fallback to first allowlist entry when target not in list", () => {
it("should error when target not in allowlist (implicit mode)", () => {
const result = twitchOutbound.resolveTarget({
to: "#notallowed",
mode: "implicit",
allowFrom: ["#primary", "#secondary"],
});
expect(result.ok).toBe(true);
expect(result.to).toBe("primary");
expect(result.ok).toBe(false);
expect(result.error).toContain("Twitch");
});
it("should accept any target when allowlist is empty", () => {
@@ -130,15 +130,15 @@ describe("outbound", () => {
expect(result.to).toBe("anychannel");
});
it("should use first allowlist entry when no target provided", () => {
it("should error when no target provided with allowlist", () => {
const result = twitchOutbound.resolveTarget({
to: undefined,
mode: "implicit",
allowFrom: ["#fallback", "#other"],
});
expect(result.ok).toBe(true);
expect(result.to).toBe("fallback");
expect(result.ok).toBe(false);
expect(result.error).toContain("Twitch");
});
it("should return error when no target and no allowlist", () => {
@@ -163,6 +163,17 @@ describe("outbound", () => {
expect(result.error).toContain("Missing target");
});
it("should error when target normalizes to empty string", () => {
const result = twitchOutbound.resolveTarget({
to: "#",
mode: "explicit",
allowFrom: [],
});
expect(result.ok).toBe(false);
expect(result.error).toContain("Twitch");
});
it("should filter wildcard from allowlist when checking membership", () => {
const result = twitchOutbound.resolveTarget({
to: "#mychannel",

View File

@@ -54,6 +54,12 @@ export const twitchOutbound: ChannelOutboundAdapter = {
// If target is provided, normalize and validate it
if (trimmed) {
const normalizedTo = normalizeTwitchChannel(trimmed);
if (!normalizedTo) {
return {
ok: false,
error: missingTargetError("Twitch", "<channel-name>"),
};
}
// For implicit/heartbeat modes with allowList, check against allowlist
if (mode === "implicit" || mode === "heartbeat") {
@@ -63,26 +69,22 @@ export const twitchOutbound: ChannelOutboundAdapter = {
if (allowList.includes(normalizedTo)) {
return { ok: true, to: normalizedTo };
}
// Fallback to first allowFrom entry
return { ok: true, to: allowList[0] };
return {
ok: false,
error: missingTargetError("Twitch", "<channel-name>"),
};
}
// For explicit mode, accept any valid channel name
return { ok: true, to: normalizedTo };
}
// No target provided, use allowFrom fallback
if (allowList.length > 0) {
return { ok: true, to: allowList[0] };
}
// No target provided - error
// No target and no allowFrom - error
return {
ok: false,
error: missingTargetError(
"Twitch",
"<channel-name> or channels.twitch.accounts.<account>.allowFrom[0]",
),
error: missingTargetError("Twitch", "<channel-name>"),
};
},