fix(channels): harden runtime credential management APIs (#3581)

* fix(channels): harden runtime credential management APIs

* fix(channels): address review feedback on credential hardening

Follow-up to the runtime credential-hardening pass, resolving five review
findings:

- WeChat auth persistence now writes through a 0o600 NamedTemporaryFile +
  Path.replace instead of write_text-then-chmod, so the iLink bot_token is
  never briefly readable at umask defaults (mirrors ChannelRuntimeConfigStore).
- The post-write chmod is split into its own try/except: a chmod failure on a
  filesystem without POSIX perms now logs at debug instead of masquerading as
  a "failed to persist" warning.
- Extracted the three near-identical _require_admin_user helpers (mcp,
  channel_connections, channels) into a single require_admin_user(request, *,
  detail) in app/gateway/deps.py; each router supplies its own detail string.
- Strengthened the runtime-config-store chmod coverage: a new test injects a
  temp-file chmod failure and asserts it is logged at debug while the
  destination is still owner-only (mutation-verified to fail if the chmod is
  dropped), plus a loose-pre-existing-file case.
- Removed the unused _FakeRepo from the blocking-io test: its isinstance gate
  routes through the repo-less 503 path, so neither stub was ever invoked.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
This commit is contained in:
Nan Gao
2026-06-18 04:45:33 +02:00
committed by GitHub
parent 68ba4198b8
commit 2b301e8211
11 changed files with 314 additions and 56 deletions
+86
View File
@@ -0,0 +1,86 @@
"""Router tests for legacy IM channel management endpoints."""
from __future__ import annotations
from types import SimpleNamespace
from unittest.mock import AsyncMock
from uuid import UUID
from _router_auth_helpers import make_authed_test_app
from fastapi.testclient import TestClient
from app.gateway.auth.models import User
from app.gateway.routers import channels
def _admin_user() -> User:
return User(
id=UUID("11111111-2222-3333-4444-555555555555"),
email="admin@example.com",
password_hash="x",
system_role="admin",
)
def _non_admin_user() -> User:
return User(
id=UUID("99999999-8888-7777-6666-555555555555"),
email="user@example.com",
password_hash="x",
system_role="user",
)
def test_restart_channel_requires_admin(monkeypatch):
service = SimpleNamespace(restart_channel=AsyncMock(return_value=True))
monkeypatch.setattr("app.channels.service.get_channel_service", lambda: service)
app = make_authed_test_app(user_factory=_non_admin_user)
app.include_router(channels.router)
with TestClient(app) as client:
response = client.post("/api/channels/slack/restart")
assert response.status_code == 403
assert "Admin privileges" in response.json()["detail"]
service.restart_channel.assert_not_awaited()
def test_restart_channel_allows_admin(monkeypatch):
service = SimpleNamespace(restart_channel=AsyncMock(return_value=True))
monkeypatch.setattr("app.channels.service.get_channel_service", lambda: service)
app = make_authed_test_app(user_factory=_admin_user)
app.include_router(channels.router)
with TestClient(app) as client:
response = client.post("/api/channels/slack/restart")
assert response.status_code == 200
assert response.json() == {
"success": True,
"message": "Channel slack restarted successfully",
}
service.restart_channel.assert_awaited_once_with("slack")
def test_get_channels_status_remains_read_only(monkeypatch):
service = SimpleNamespace(
get_status=lambda: {
"service_running": True,
"channels": {
"slack": {
"enabled": True,
"running": True,
}
},
}
)
monkeypatch.setattr("app.channels.service.get_channel_service", lambda: service)
app = make_authed_test_app(user_factory=_non_admin_user)
app.include_router(channels.router)
with TestClient(app) as client:
response = client.get("/api/channels/")
assert response.status_code == 200
assert response.json()["service_running"] is True
assert response.json()["channels"]["slack"]["running"] is True