{"record":{"id":"6ee16745fabd0065","repo":"fullstackhero/dotnet-starter-kit","slug":"no-current-user-removechannelmembercommandhandler","errorCode":null,"errorMessage":"no current user","messagePattern":"no current user","errorType":"exception","errorClass":"UnauthorizedException","httpStatus":401,"severity":"error","filePath":"src/Modules/Chat/Modules.Chat/Features/v1/Channels/RemoveChannelMember/RemoveChannelMemberCommandHandler.cs","lineNumber":24,"sourceCode":"using FSH.Modules.Chat.Domain;\nusing FSH.Modules.Chat.Features.v1.Internal;\nusing Mediator;\nusing Microsoft.AspNetCore.SignalR;\nusing Microsoft.EntityFrameworkCore;\n\nnamespace FSH.Modules.Chat.Features.v1.Channels.RemoveChannelMember;\n\npublic sealed class RemoveChannelMemberCommandHandler(\n    ChatDbContext db,\n    ICurrentUser currentUser,\n    IHubContext<AppHub> hub)\n    : ICommandHandler<RemoveChannelMemberCommand, Unit>\n{\n    public async ValueTask<Unit> Handle(RemoveChannelMemberCommand cmd, CancellationToken cancellationToken)\n    {\n        ArgumentNullException.ThrowIfNull(cmd);\n        var userId = currentUser.GetUserId();\n        if (userId == Guid.Empty) throw new UnauthorizedException(\"no current user\");\n        var currentUserId = userId.ToString();\n\n        var channel = await db.Channels.FirstOrDefaultAsync(c => c.Id == cmd.ChannelId, cancellationToken)\n            .ConfigureAwait(false)\n            ?? throw new NotFoundException(\"Channel not found.\");\n\n        // Self-leave is always allowed for the current user. Removing someone else requires Admin.\n        var isSelfLeave = string.Equals(cmd.UserId, currentUserId, StringComparison.Ordinal);\n        if (!isSelfLeave)\n        {\n            channel.RequireAdmin(currentUserId);\n        }\n        else\n        {\n            channel.RequireMember(currentUserId);\n        }\n\n        channel.RemoveMember(cmd.UserId, currentUserId);","sourceCodeStart":6,"sourceCodeEnd":42,"githubUrl":"https://github.com/fullstackhero/dotnet-starter-kit/blob/3f2959e683e9f83f13e55e1678c9119f63c7e8e5/src/Modules/Chat/Modules.Chat/Features/v1/Channels/RemoveChannelMember/RemoveChannelMemberCommandHandler.cs#L6-L42","documentation":"RemoveChannelMemberCommandHandler requires an authenticated caller; currentUser.GetUserId() returning Guid.Empty raises UnauthorizedException before the channel lookup. Removing a member (or self-leaving) must be attributed to a user to enforce the self-leave vs admin rules.","triggerScenarios":"Calling the remove-member command (DELETE /channels/{channelId}/members/{userId}) without a valid JWT, with an expired token, or from an anonymous context.","commonSituations":"Expired session while a member-management dialog is open; missing Authorization header in an API test; proxy stripping auth headers; token refresh race in the admin UI.","solutions":["Re-authenticate and retry with a valid bearer token.","Add a 401 interceptor that refreshes the token then replays the mutation.","Verify auth middleware configuration on the host.","Ensure the user's id claim exists in the token."],"exampleFix":"// before\napi.delete(`/channels/${cid}/members/${uid}`);\n// after\nawait withAuth(() => api.delete(`/channels/${cid}/members/${uid}`));","handlingStrategy":"try-catch","validationCode":"if (!accessToken || isTokenExpired(accessToken)) await refreshToken();","typeGuard":null,"tryCatchPattern":"try { ... } catch (e) {\n  if (isUnauthorized(e)) { await reauthenticate(); retryRemoval(); }\n  else throw e;\n}","preventionTips":["Disable member-management actions while the session is being refreshed.","Centralize 401 handling with refresh-and-replay.","Confirm auth headers pass through proxies/gateways in test environments.","Include the user id claim in issued tokens."],"tags":["auth","jwt","unauthorized"],"backgroundTag":"authentication-required","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"}