feat(pairing): allow omitted allowFrom — pairing-only mode by default

Previously _validate_allow_from raised SystemExit when allowFrom was
missing, forcing every channel to declare an explicit allowlist.
With the pairing feature this is no longer necessary: a channel with
no allowFrom simply operates in pairing-only mode, letting users
approve senders via /pairing approve <code> from the WebUI or CLI.

- Replace SystemExit with an info log in _validate_allow_from
- Add test_validate_allow_from_allows_missing_allow_from
This commit is contained in:
chengyongru 2026-05-15 10:33:18 +08:00
parent 6b3df61e75
commit 206b473c11
2 changed files with 35 additions and 3 deletions

View File

@ -144,9 +144,11 @@ class ChannelManager:
else:
allow = getattr(cfg, "allow_from", None)
if allow is None:
raise SystemExit(
f'Error: "{name}" is missing or null allowFrom. '
f'Set ["*"] to allow everyone, or add specific user IDs.'
# allowFrom omitted → pairing-only mode. Unapproved senders
# receive a pairing code instead of being silently ignored.
logger.info(
'"{}" has no allowFrom; unapproved users will receive a pairing code',
name,
)
def _should_send_progress(self, channel_name: str, *, tool_hint: bool = False) -> bool:

View File

@ -1010,6 +1010,36 @@ async def test_validate_allow_from_allows_empty_dict_allow_from():
mgr._validate_allow_from()
@pytest.mark.asyncio
async def test_validate_allow_from_allows_missing_allow_from():
"""Omitted allowFrom is valid — channel operates in pairing-only mode."""
fake_config = SimpleNamespace(
channels=ChannelsConfig(),
providers=SimpleNamespace(groq=SimpleNamespace(api_key="")),
)
class _NoAllowFromChannel(BaseChannel):
name = "noallow"
display_name = "No Allow"
async def start(self) -> None:
pass
async def stop(self) -> None:
pass
async def send(self, msg: OutboundMessage) -> None:
pass
mgr = ChannelManager.__new__(ChannelManager)
mgr.config = fake_config
mgr.channels = {"test": _NoAllowFromChannel({"enabled": True}, None)}
mgr._dispatch_task = None
# Should not raise — pairing-only mode
mgr._validate_allow_from()
@pytest.mark.asyncio
async def test_get_channel_returns_channel_if_exists():
"""get_channel should return the channel if it exists."""