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 committed by Xubin Ren
parent 199a1bb8fa
commit 88ff64be48
2 changed files with 35 additions and 3 deletions

View File

@ -144,9 +144,11 @@ class ChannelManager:
else: else:
allow = getattr(cfg, "allow_from", None) allow = getattr(cfg, "allow_from", None)
if allow is None: if allow is None:
raise SystemExit( # allowFrom omitted → pairing-only mode. Unapproved senders
f'Error: "{name}" is missing or null allowFrom. ' # receive a pairing code instead of being silently ignored.
f'Set ["*"] to allow everyone, or add specific user IDs.' 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: 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() 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 @pytest.mark.asyncio
async def test_get_channel_returns_channel_if_exists(): async def test_get_channel_returns_channel_if_exists():
"""get_channel should return the channel if it exists.""" """get_channel should return the channel if it exists."""