{"record":{"id":"53f05488ba94291a","repo":"ruvnet/RuView","slug":"permission-permission-required","errorCode":null,"errorMessage":"Permission '{permission}' required","messagePattern":"Permission '(.+?)' required","errorType":"http","errorClass":"HTTPException","httpStatus":403,"severity":"error","filePath":"archive/v1/src/api/dependencies.py","lineNumber":162,"sourceCode":"\n\n# Permission dependencies\ndef require_permission(permission: str):\n    \"\"\"Dependency factory for permission checking.\"\"\"\n    \n    async def check_permission(\n        current_user: Dict[str, Any] = Depends(get_current_active_user)\n    ) -> Dict[str, Any]:\n        \"\"\"Check if user has required permission.\"\"\"\n        user_permissions = current_user.get(\"permissions\", [])\n        \n        # Admin users have all permissions\n        if current_user.get(\"is_admin\", False):\n            return current_user\n        \n        # Check specific permission\n        if permission not in user_permissions:\n            raise HTTPException(\n                status_code=status.HTTP_403_FORBIDDEN,\n                detail=f\"Permission '{permission}' required\"\n            )\n        \n        return current_user\n    \n    return check_permission\n\n\n# Zone access dependencies\nasync def validate_zone_access(\n    zone_id: str,\n    current_user: Optional[Dict[str, Any]] = Depends(get_current_user)\n) -> str:\n    \"\"\"Validate user access to a specific zone.\"\"\"\n    domain_config = get_domain_config()\n    \n    # Check if zone exists","sourceCodeStart":144,"sourceCodeEnd":180,"githubUrl":"https://github.com/ruvnet/RuView/blob/4685618388a5e49fad5b3005806f3bdd6a7c25c3/archive/v1/src/api/dependencies.py#L144-L180","documentation":"require_permission(permission) is a dependency factory; the closure it returns raises 403 with the interpolated message when the user is not an admin and the permission string is not in the user's permissions list. Admin users bypass the check entirely.","triggerScenarios":"Calling an endpoint whose require_permission('...') string does not match any entry in the user's permissions claim; permission renamed in code while tokens/database still carry the old name; user with an empty permissions list hitting a gated endpoint.","commonSituations":"Permission model refactors without re-issuing tokens; feature-gated endpoints; mismatched permission naming between the identity source and the API code.","solutions":["Inspect the user's permissions (decode the token or query the account) and obtain the missing permission","Align permission strings between the token issuer/database and the require_permission call site","Re-login or re-issue the token so updated permissions propagate","If the caller should bypass the check, use an admin account"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"# Verify the required permission locally before the call\nREQUIRED = 'zones:write'\nclaims = jwt.decode(token, options={'verify_signature': False})\nperms = set(claims.get('permissions', []))\nif not claims.get('is_admin', False) and REQUIRED not in perms:\n    raise PermissionError(f'Missing permission: {REQUIRED}')\nclient.post('/api/zones', headers={'Authorization': f'Bearer {token}'}, json=payload)","typeGuard":null,"tryCatchPattern":"try:\n    r = client.post('/api/zones', headers=auth, json=payload)\n    r.raise_for_status()\nexcept httpx.HTTPStatusError as e:\n    if e.response.status_code == 403 and 'Permission' in (e.response.json().get('detail') or ''):\n        request_permission(e.response.json()['detail'])  # surface which permission is missing\n    raise","preventionTips":["Define permission strings as constants shared by issuer and API to prevent drift","Re-issue tokens after any permission-model change","Surface the missing permission name from the 403 detail in the UI"],"tags":["authorization","permissions","http-403","python"],"backgroundTag":null,"analyzedSha":"4685618388a5e49fad5b3005806f3bdd6a7c25c3","analyzedAt":"2026-08-16T06:09:40.886Z","schemaVersion":2},"datasetVersion":"2026-08-16T08:17:34.114Z"}