{"record":{"id":"d17c1010c500cb4e","repo":"bytedance/deer-flow","slug":"authentication-required-d17c10","errorCode":null,"errorMessage":"Authentication required","messagePattern":"Authentication required","errorType":"http","errorClass":"HTTPException","httpStatus":401,"severity":"error","filePath":"backend/app/gateway/routers/channel_connections.py","lineNumber":142,"sourceCode":"    ),\n}\n\n_RUNTIME_REQUIREMENTS: dict[str, tuple[str, ...]] = {\n    \"telegram\": (\"bot_token\",),\n    \"slack\": (\"bot_token\", \"app_token\"),\n    \"discord\": (\"bot_token\",),\n    \"feishu\": (\"app_id\", \"app_secret\"),\n    \"dingtalk\": (\"client_id\", \"client_secret\"),\n    \"wechat\": (\"bot_token\",),\n    \"wecom\": (\"bot_id\", \"bot_secret\"),\n    \"buzz\": (\"relay_url\", \"private_key\"),\n}\n\n\ndef _get_user_id(request: Request) -> str:\n    user = getattr(request.state, \"user\", None)\n    if user is None:\n        raise HTTPException(status_code=401, detail=\"Authentication required\")\n    return str(user.id)\n\n\ndef _get_app_config():\n    from deerflow.config.app_config import get_app_config\n\n    return get_app_config()\n\n\nasync def _get_runtime_config_store(request: Request) -> ChannelRuntimeConfigStore:\n    store = getattr(request.app.state, \"channel_runtime_config_store\", None)\n    if isinstance(store, ChannelRuntimeConfigStore):\n        return store\n    # Constructing the store reads its JSON file from disk; keep it off the\n    # event loop.\n    store = await asyncio.to_thread(ChannelRuntimeConfigStore)\n    request.app.state.channel_runtime_config_store = store\n    return store","sourceCodeStart":124,"sourceCodeEnd":160,"githubUrl":"https://github.com/bytedance/deer-flow/blob/1dd6ba1acb03700589994b0366c5d1c7d05e2eff/backend/app/gateway/routers/channel_connections.py#L124-L160","documentation":"401 Unauthorized from the channel-connections router's helper _get_user_id (channel_connections.py:142): request.state.user is None, meaning no authenticated user was resolved for the request. The channel connection API is user-scoped (every connection binds to an owner), so an anonymous request cannot proceed. It typically indicates the auth middleware did not run/populate state — the route's normal auth dependency should have rejected the request earlier.","triggerScenarios":"Calling /api/channels/connections/* endpoints without a valid session/JWT (or with an expired token) in a context where the router-level auth dependency was bypassed — e.g. direct internal invocation, misconfigured middleware ordering, or a token that failed cookie/bearer resolution.","commonSituations":"Scripting the channel-connection endpoints with a missing/expired access token; auth disabled mode where routes are mounted but state.user is never set; middleware chain reordered so channel routes mount before auth wiring.","solutions":["Authenticate first: obtain a session (login) or valid token and send it with the request (cookie or Authorization header, per the Gateway auth scheme)","If this appears in tests, ensure the test client sets up request.state.user or uses the app's auth fixtures rather than hitting the router function bare","Verify the channel-connections router is mounted with its auth dependency intact (not include_router(..., dependencies=[]) overrides)"],"exampleFix":"# before\ncurl -X GET http://localhost:2026/api/channels/connections\n# -> 401 Authentication required\n\n# after\nTOKEN=$(curl -s -X POST http://localhost:2026/api/auth/login -d 'username=...&password=...' | jq -r .access_token)\ncurl -H \"Authorization: Bearer $TOKEN\" http://localhost:2026/api/channels/connections","handlingStrategy":"validation","validationCode":"const resp = await fetch(\"/api/channels/connections\");\nif (resp.status === 401) { await redirectToLogin(); return; }\nconst data = await resp.json();","typeGuard":null,"tryCatchPattern":"try { await listConnections() } catch (e) { if (e.status === 401) { await reauthenticate(); retryOnce(); } else throw e; }","preventionTips":["Attach credentials (cookie/Authorization) to every channel-connections call","Handle 401 by refreshing the session once, then retrying exactly once","Don't disable the router's auth dependency when mounting — request.state.user must be populated"],"tags":["auth","channels","http-401"],"backgroundTag":null,"analyzedSha":"1dd6ba1acb03700589994b0366c5d1c7d05e2eff","analyzedAt":"2026-08-14T21:20:34.804Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}