{"record":{"id":"5474871a95ce0439","repo":"fullstackhero/dotnet-starter-kit","slug":"administrators-cannot-remove-their-own-admin-role","errorCode":null,"errorMessage":"Administrators cannot remove their own admin role.","messagePattern":"Administrators cannot remove their own admin role\\.","errorType":"exception","errorClass":"CustomException","httpStatus":400,"severity":"error","filePath":"src/Modules/Identity/Modules.Identity/Services/UserRoleService.cs","lineNumber":90,"sourceCode":"    {\n        bool isRemovingAdminRole = userRoles.Exists(a => !a.Enabled && a.RoleName == RoleConstants.Admin);\n        if (!isRemovingAdminRole)\n        {\n            return;\n        }\n\n        bool userIsAdmin = await userManager.IsInRoleAsync(user, RoleConstants.Admin);\n        if (!userIsAdmin)\n        {\n            return;\n        }\n\n        // Administrators cannot demote themselves — they would lose access immediately on the next request,\n        // and would need another admin to restore them.\n        var actorId = currentUser.GetUserId();\n        if (actorId != Guid.Empty && string.Equals(actorId.ToString(), user.Id, StringComparison.Ordinal))\n        {\n            throw new CustomException(\n                \"Administrators cannot remove their own admin role.\",\n                Array.Empty<string>(),\n                HttpStatusCode.BadRequest);\n        }\n\n        // The root tenant's seed admin is the framework's last-resort recovery account.\n        if (IsRootTenantAdmin(user))\n        {\n            throw new ForbiddenException(\"The root tenant administrator cannot be demoted.\");\n        }\n\n        // After this removal, at least one admin must remain in the tenant — matches\n        // the \"at least one active administrator\" invariant enforced on user deactivation.\n        await EnsureMinimumAdminCountAsync();\n    }\n\n    private bool IsRootTenantAdmin(FshUser user)\n    {","sourceCodeStart":72,"sourceCodeEnd":108,"githubUrl":"https://github.com/fullstackhero/dotnet-starter-kit/blob/3f2959e683e9f83f13e55e1678c9119f63c7e8e5/src/Modules/Identity/Modules.Identity/Services/UserRoleService.cs#L72-L108","documentation":"Thrown by ValidateAdminRoleChangeAsync (invoked from AssignRolesAsync) when an administrator attempts to remove the Admin role from their own account. Guard prevents self-lockout — the actor would lose admin access on the very next request with no one to restore it.","triggerScenarios":"An authenticated Admin sends a role-assignment payload for their own userId whose entries remove or unassign the Admin role.","commonSituations":"Admin testing role changes on themselves; bulk role-edit UI submitting the current user's row with Admin unchecked; scripts looping over users including the acting admin.","solutions":["Have a different administrator perform the demotion of this account.","Exclude the current user's id from bulk role-update payloads client-side.","If self-change is genuinely required, remove Admin and re-add it in one call is still blocked — plan a second admin first.","In tooling, skip the actor's own account when iterating role updates."],"exampleFix":"// before: submits every row, including the acting admin\nforeach (var row in allUsers) await assignRolesAsync(row.Id, row.Roles, ct);\n// after: skip self\nforeach (var row in allUsers.Where(u => u.Id != currentUserId)) await assignRolesAsync(row.Id, row.Roles, ct);","handlingStrategy":"validation","validationCode":"const payload = roles.filter(r => !(userId === currentUserId && r.role === \"Admin\" && !r.assigned));\nif (payload.length !== roles.length) warn(\"You cannot remove your own Admin role.\");","typeGuard":null,"tryCatchPattern":"try { await assignRoles(userId, roles); } catch (e) {\n  if (e.status === 400 && /own admin role/i.test(e.message)) notify(\"Ask another admin to make this change\");\n  else throw e;\n}","preventionTips":["Disable self-demotion controls in the admin UI.","Exclude the actor's id from bulk role-update loops.","Keep at least two admins so changes can be made by the other one."],"tags":["identity","roles","authorization"],"backgroundTag":"invalid-state-transition","analyzedSha":"3f2959e683e9f83f13e55e1678c9119f63c7e8e5","analyzedAt":"2026-09-15T22:20:53.684Z","contentChangedAt":"2026-09-15T22:20:53.684Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}