{"record":{"id":"b2c79029102b9a97","repo":"fullstackhero/dotnet-starter-kit","slug":"users-cannot-be-removed-from-a-default-group","errorCode":null,"errorMessage":"Users cannot be removed from a default group.","messagePattern":"Users cannot be removed from a default group\\.","errorType":"exception","errorClass":"ForbiddenException","httpStatus":403,"severity":"error","filePath":"src/Modules/Identity/Modules.Identity/Features/v1/Groups/RemoveUserFromGroup/RemoveUserFromGroupCommandHandler.cs","lineNumber":38,"sourceCode":"\n    public async ValueTask<Unit> Handle(RemoveUserFromGroupCommand command, CancellationToken cancellationToken)\n    {\n        ArgumentNullException.ThrowIfNull(command);\n\n        var membership = await _dbContext.UserGroups\n            .Include(ug => ug.Group)\n            .FirstOrDefaultAsync(ug => ug.GroupId == command.GroupId && ug.UserId == command.UserId, cancellationToken);\n\n        if (membership is null)\n        {\n            throw new NotFoundException($\"User '{command.UserId}' is not a member of group '{command.GroupId}'.\");\n        }\n\n        // Default groups (e.g. seeded \"All Users\") require every tenant user to be a member, so\n        // removing one breaks that invariant and leaves later registrants in a half-populated group.\n        if (membership.Group is not null && membership.Group.IsDefault)\n        {\n            throw new ForbiddenException(\"Users cannot be removed from a default group.\");\n        }\n\n        _dbContext.UserGroups.Remove(membership);\n        await _dbContext.SaveChangesAsync(cancellationToken);\n\n        // Leaving a group may revoke roles the user only held through this group —\n        // invalidate so the cached permission set is rebuilt on next request.\n        await _userPermissionService.InvalidatePermissionCacheAsync(command.UserId, cancellationToken).ConfigureAwait(false);\n\n        return Unit.Value;\n    }\n}","sourceCodeStart":20,"sourceCodeEnd":50,"githubUrl":"https://github.com/fullstackhero/dotnet-starter-kit/blob/3f2959e683e9f83f13e55e1678c9119f63c7e8e5/src/Modules/Identity/Modules.Identity/Features/v1/Groups/RemoveUserFromGroup/RemoveUserFromGroupCommandHandler.cs#L20-L50","documentation":"RemoveUserFromGroup blocks removal from groups flagged IsDefault (e.g. the seeded \"All Users\" group) by throwing ForbiddenException (HTTP 403). Default groups guarantee every tenant user is a member; removing members would break that invariant for later registrants.","triggerScenarios":"DELETE /groups/{groupId}/users/{userId} where membership.Group.IsDefault is true — attempting to kick a user out of the tenant's default group.","commonSituations":"Bulk cleanup scripts iterating every group's members; offboarding flows that try to strip all group memberships; UI not marking default groups as read-only.","solutions":["Skip IsDefault groups in bulk membership-removal logic","For offboarding, deactivate/delete the user account instead of removing them from the default group","Catch 403 and continue in bulk operations, logging the skipped pair"],"exampleFix":"// before\nforeach (var g in user.Groups) await api.RemoveUserFromGroup(g.Id, userId);\n// after\nforeach (var g in user.Groups.Where(g => !g.IsDefault)) await api.RemoveUserFromGroup(g.Id, userId);","handlingStrategy":"validation","validationCode":"var targets = user.Groups.Where(g => !g.IsDefault);\nforeach (var g in targets) await api.RemoveUserFromGroup(g.Id, userId);","typeGuard":"bool Removable(GroupDto g) => !g.IsDefault;","tryCatchPattern":"try { await api.RemoveUserFromGroup(groupId, userId); }\ncatch (ApiException e) when (e.Status == 403) { logger.LogInformation(\"Skipped default group {GroupId}\", groupId); }","preventionTips":["Render default groups as read-only in membership UIs","Exclude IsDefault groups from offboarding scripts","Offboard by deactivating the user, not by stripping default-group membership"],"tags":["http-403","forbidden","identity","default-groups"],"backgroundTag":"permission-denied","analyzedSha":"3f2959e683e9f83f13e55e1678c9119f63c7e8e5","analyzedAt":"2026-09-15T22:20:53.684Z","contentChangedAt":"2026-09-15T22:20:53.684Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}