{"record":{"id":"245342e9b57915d4","repo":"langflow-ai/langflow","slug":"superuser-required-to-administer-roles","errorCode":null,"errorMessage":"Superuser required to administer roles.","messagePattern":"Superuser required to administer roles\\.","errorType":"http","errorClass":"HTTPException","httpStatus":403,"severity":"error","filePath":"src/backend/base/langflow/api/v1/authz_roles.py","lineNumber":37,"sourceCode":"    safe_invalidate_role,\n)\nfrom langflow.services.authorization.utils import audit_decision\nfrom langflow.services.database.models.auth import AuthzRole, AuthzRoleAssignment\nfrom langflow.services.deps import get_authorization_service\n\nrouter = APIRouter(prefix=\"/authz/roles\", tags=[\"Authorization\"])\n\n# Match ``authz_shares``: cap any single list call so an authenticated client\n# (or a buggy frontend) can't enumerate the entire role/team catalog in one\n# request. 100 default / 200 max is enough for typical UI dropdowns.\n_LIST_MAX_LIMIT = 200\n_LIST_DEFAULT_LIMIT = 100\n\n\ndef _require_superuser(user) -> None:\n    \"\"\"Superuser-only gate. Role admin is an operations action.\"\"\"\n    if not getattr(user, \"is_superuser\", False):\n        raise HTTPException(\n            status_code=status.HTTP_403_FORBIDDEN,\n            detail=\"Superuser required to administer roles.\",\n        )\n\n\nasync def _detect_parent_cycle(\n    session: DbSession,\n    *,\n    role_id: UUID,\n    proposed_parent_id: UUID,\n) -> bool:\n    \"\"\"Walk the parent chain from ``proposed_parent_id``; True if ``role_id`` appears.\n\n    Used to reject ``PATCH`` requests that would set a role as its own ancestor.\n    Walks at most ``len(all_roles)`` steps so a pre-existing cycle terminates.\n    \"\"\"\n    visited: set[UUID] = set()\n    cursor: UUID | None = proposed_parent_id","sourceCodeStart":19,"sourceCodeEnd":55,"githubUrl":"https://github.com/langflow-ai/langflow/blob/976ec789d2886a86de109c044d089d68e96c9a35/src/backend/base/langflow/api/v1/authz_roles.py#L19-L55","documentation":"Superuser gate on the role-administration routes under /api/v1/authz/roles (create/update/delete; list and read are open to any authenticated user). A non-superuser calling a mutating role route gets HTTP 403 'Superuser required to administer roles.'","triggerScenarios":"POST/PATCH/DELETE /api/v1/authz/roles* with a token whose user has is_superuser=False. Listing (GET '') and reading a single role (GET /{role_id}) do not hit this, only the mutations do.","commonSituations":"Frontends that show the role-admin UI to all logged-in users, scripts authenticated as a normal workspace user, or forgetting to switch tokens when moving from read-only inspection to role editing.","solutions":["Use the seed superuser credentials (LANGFLOW_SUPERUSER env vars) to obtain the token for role admin","Restrict the role-management UI to superusers so the 403 never fires in production","Verify is_superuser on the /api/v1/users/whoami response before showing admin controls"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"const me = await api.get('/users/whoami');\nif (!me.is_superuser) disableRoleAdminUI();","typeGuard":"const canAdminRoles = (u: { is_superuser?: boolean } | null): boolean =>\n  Boolean(u?.is_superuser);","tryCatchPattern":"catch (e) { if (e.status === 403) notify('Superuser required'); return; }","preventionTips":["Read and write are different gates — reads are open, mutations are superuser-only; plan tokens accordingly","Use the seed superuser for provisioning scripts","Hide create/update/delete role controls for non-superusers"],"tags":["authz","rbac","http-403","authorization","roles"],"backgroundTag":null,"analyzedSha":"976ec789d2886a86de109c044d089d68e96c9a35","analyzedAt":"2026-08-14T18:23:12.227Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}