{"record":{"id":"1c81fa964bdc60bb","repo":"fullstackhero/dotnet-starter-kit","slug":"no-current-user","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/AddChannelMembers/AddChannelMembersCommandHandler.cs","lineNumber":24,"sourceCode":"using FSH.Modules.Chat.Data;\nusing FSH.Modules.Chat.Features.v1.Internal;\nusing Mediator;\nusing Microsoft.AspNetCore.SignalR;\nusing Microsoft.EntityFrameworkCore;\n\nnamespace FSH.Modules.Chat.Features.v1.Channels.AddChannelMembers;\n\npublic sealed class AddChannelMembersCommandHandler(\n    ChatDbContext db,\n    ICurrentUser currentUser,\n    IHubContext<AppHub> hub)\n    : ICommandHandler<AddChannelMembersCommand, Unit>\n{\n    public async ValueTask<Unit> Handle(AddChannelMembersCommand 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        // Members can invite to public channels they belong to; private channels require Admin.\n        var caller = channel.RequireMember(currentUserId);\n        if (channel.IsPrivate && caller.Role != ChannelMemberRole.Admin)\n        {\n            throw new ForbiddenException(\"Only channel admins can add members to private channels.\");\n        }\n\n        var newlyAdded = new List<string>();\n        foreach (var uid in cmd.UserIds.Distinct(StringComparer.Ordinal))\n        {\n            // Skip duplicates silently — endpoint is idempotent for already-members.\n            if (channel.Members.Any(m => string.Equals(m.UserId, uid, StringComparison.Ordinal))) continue;","sourceCodeStart":6,"sourceCodeEnd":42,"githubUrl":"https://github.com/fullstackhero/dotnet-starter-kit/blob/3f2959e683e9f83f13e55e1678c9119f63c7e8e5/src/Modules/Chat/Modules.Chat/Features/v1/Channels/AddChannelMembers/AddChannelMembersCommandHandler.cs#L6-L42","documentation":"AddChannelMembersCommandHandler rejects the command with UnauthorizedException when currentUser.GetUserId() returns Guid.Empty, meaning no authenticated user is associated with the request context. The handler needs a current user to attribute the membership change and run permission checks.","triggerScenarios":"Invoking the AddChannelMembers endpoint without a valid JWT, with an expired/anonymous identity, or in tests without a mocked ICurrentUser returning a real Guid.","commonSituations":"Missing/invalid Authorization header; token expired; integration tests forgetting to authenticate the request; calling the handler directly with an unconfigured ICurrentUser substitute.","solutions":["Ensure the request carries a valid, non-expired JWT bearer token","Check authentication/401 handling at the client and re-login on token expiry","In tests, mock ICurrentUser.GetUserId() to return a real Guid or use the authenticated test client"],"exampleFix":"// before (test)\nvar handler = new AddChannelMembersCommandHandler(db, currentUserSubstitute); // GetUserId -> Guid.Empty\n// after\ncurrentUserSubstitute.GetUserId().Returns(Guid.NewGuid());","handlingStrategy":"validation","validationCode":"var userId = currentUser.GetUserId();\nif (userId == Guid.Empty) throw new UnauthorizedException(\"no current user\");","typeGuard":"bool isAuthenticated = currentUser.GetUserId() != Guid.Empty;","tryCatchPattern":"try { await handler.Handle(cmd, ct); }\ncatch (UnauthorizedException) { /* return 401 / trigger re-login */ }","preventionTips":["Always send Authorization header","Refresh tokens before expiry","In tests, stub ICurrentUser with a real Guid"],"tags":["auth","unauthorized","chat","current-user"],"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"}