{"record":{"id":"eb26afb951745c7d","repo":"ruvnet/RuView","slug":"access-denied-to-router-router-id","errorCode":null,"errorMessage":"Access denied to router '{router_id}'","messagePattern":"Access denied to router '(.+?)'","errorType":"http","errorClass":"HTTPException","httpStatus":403,"severity":"error","filePath":"archive/v1/src/api/dependencies.py","lineNumber":244,"sourceCode":"        )\n    \n    # Check if router is enabled\n    if not router.enabled:\n        raise HTTPException(\n            status_code=status.HTTP_403_FORBIDDEN,\n            detail=f\"Router '{router_id}' is disabled\"\n        )\n    \n    # If authentication is enabled, check user access\n    if current_user:\n        # Admin users have access to all routers\n        if current_user.get(\"is_admin\", False):\n            return router_id\n        \n        # Check user's router permissions\n        user_routers = current_user.get(\"routers\", [])\n        if user_routers and router_id not in user_routers:\n            raise HTTPException(\n                status_code=status.HTTP_403_FORBIDDEN,\n                detail=f\"Access denied to router '{router_id}'\"\n            )\n    \n    return router_id\n\n\n# Service health dependencies\nasync def check_service_health(\n    request: Request,\n    service_name: str\n) -> bool:\n    \"\"\"Check if a service is healthy.\"\"\"\n    try:\n        if service_name == \"pose\":\n            service = getattr(request.app.state, 'pose_service', None)\n        elif service_name == \"stream\":\n            service = getattr(request.app.state, 'stream_service', None)","sourceCodeStart":226,"sourceCodeEnd":262,"githubUrl":"https://github.com/ruvnet/RuView/blob/4685618388a5e49fad5b3005806f3bdd6a7c25c3/archive/v1/src/api/dependencies.py#L226-L262","documentation":"validate_router_access raises 403 \"Access denied to router '<router_id>'\" when the authenticated user is not an admin, their routers list is non-empty, and the requested router is not in it. As with zones, an empty routers list means unrestricted; denial requires a non-empty list that excludes the router.","triggerScenarios":"A user provisioned with routers=['node-a'] calling a route for 'node-b'; scoped service accounts whose allowlist predates a newly added router; stale tokens after router provisioning changes.","commonSituations":"Per-node access scoping for field technicians; adding hardware without updating user allowlists; token claims drifting from current permissions.","solutions":["Add the router to the user's routers allowlist (or have an admin do it)","Use an admin token, which bypasses the router allowlist","If the user should access all routers, clear their routers list"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"# Check the token's router allowlist before the call (empty list = unrestricted)\nclaims = jwt.decode(token, options={'verify_signature': False})\nrouters = claims.get('routers') or []\nif routers and router_id not in routers and not claims.get('is_admin'):\n    raise PermissionError(f'Token not scoped for router {router_id}')","typeGuard":null,"tryCatchPattern":"try:\n    r = client.get(f'/api/routers/{router_id}', headers=auth)\n    r.raise_for_status()\nexcept httpx.HTTPStatusError as e:\n    if e.response.status_code == 403 and 'Access denied' in e.response.text:\n        request_router_access(router_id)\n    raise","preventionTips":["Update router allowlists when new hardware is provisioned","Decode the routers claim when debugging 403s","Remember an empty routers list means unrestricted access"],"tags":["authorization","routers","http-403","python"],"backgroundTag":null,"analyzedSha":"4685618388a5e49fad5b3005806f3bdd6a7c25c3","analyzedAt":"2026-08-16T06:09:40.886Z","schemaVersion":2},"datasetVersion":"2026-08-16T08:17:34.114Z"}