fullstackhero/dotnet-starter-kit · error · CustomException

Administrators cannot remove their own admin role.

Error message

Administrators cannot remove their own admin role.

What it means

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.

Solutions

  1. Have a different administrator perform the demotion of this account.
  2. Exclude the current user's id from bulk role-update payloads client-side.
  3. If self-change is genuinely required, remove Admin and re-add it in one call is still blocked — plan a second admin first.
  4. In tooling, skip the actor's own account when iterating role updates.

Example fix

// before: submits every row, including the acting admin
foreach (var row in allUsers) await assignRolesAsync(row.Id, row.Roles, ct);
// after: skip self
foreach (var row in allUsers.Where(u => u.Id != currentUserId)) await assignRolesAsync(row.Id, row.Roles, ct);
Defensive patterns

Strategy: validation

Validate before calling

const payload = roles.filter(r => !(userId === currentUserId && r.role === "Admin" && !r.assigned));
if (payload.length !== roles.length) warn("You cannot remove your own Admin role.");

Try / catch

try { await assignRoles(userId, roles); } catch (e) {
  if (e.status === 400 && /own admin role/i.test(e.message)) notify("Ask another admin to make this change");
  else throw e;
}

Prevention

When it happens

Trigger: An authenticated Admin sends a role-assignment payload for their own userId whose entries remove or unassign the Admin role.

Common situations: 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.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of fullstackhero/dotnet-starter-kit@3f2959e683 (2026-09-15). Data as JSON: /api/errors/5474871a95ce0439. Report an issue: GitHub.

Appendix: source

Thrown at src/Modules/Identity/Modules.Identity/Services/UserRoleService.cs:90

    {
        bool isRemovingAdminRole = userRoles.Exists(a => !a.Enabled && a.RoleName == RoleConstants.Admin);
        if (!isRemovingAdminRole)
        {
            return;
        }

        bool userIsAdmin = await userManager.IsInRoleAsync(user, RoleConstants.Admin);
        if (!userIsAdmin)
        {
            return;
        }

        // Administrators cannot demote themselves — they would lose access immediately on the next request,
        // and would need another admin to restore them.
        var actorId = currentUser.GetUserId();
        if (actorId != Guid.Empty && string.Equals(actorId.ToString(), user.Id, StringComparison.Ordinal))
        {
            throw new CustomException(
                "Administrators cannot remove their own admin role.",
                Array.Empty<string>(),
                HttpStatusCode.BadRequest);
        }

        // The root tenant's seed admin is the framework's last-resort recovery account.
        if (IsRootTenantAdmin(user))
        {
            throw new ForbiddenException("The root tenant administrator cannot be demoted.");
        }

        // After this removal, at least one admin must remain in the tenant — matches
        // the "at least one active administrator" invariant enforced on user deactivation.
        await EnsureMinimumAdminCountAsync();
    }

    private bool IsRootTenantAdmin(FshUser user)
    {

View on GitHub (pinned to 3f2959e683)