{"record":{"id":"7f7a1aafccb33449","repo":"fullstackhero/dotnet-starter-kit","slug":"no-current-user-editmessagecommandhandler","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/Messages/EditMessage/EditMessageCommandHandler.cs","lineNumber":23,"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.Messages.EditMessage;\n\npublic sealed class EditMessageCommandHandler(\n    ChatDbContext db,\n    ICurrentUser currentUser,\n    IHubContext<AppHub> hub)\n    : ICommandHandler<EditMessageCommand, Unit>\n{\n    public async ValueTask<Unit> Handle(EditMessageCommand 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 message = await db.Messages.FirstOrDefaultAsync(m => m.Id == cmd.MessageId, cancellationToken)\n            .ConfigureAwait(false)\n            ?? throw new NotFoundException(\"Message not found.\");\n\n        // Verify membership through the parent channel (don't leak existence to non-members).\n        var channel = await db.Channels.FirstOrDefaultAsync(c => c.Id == message.ChannelId, cancellationToken)\n            .ConfigureAwait(false)\n            ?? throw new NotFoundException(\"Message not found.\");\n        channel.RequireMember(currentUserId);\n\n        message.Edit(cmd.Body, currentUserId); // domain enforces author-only\n        await db.SaveChangesAsync(cancellationToken).ConfigureAwait(false);\n\n        await hub.Clients.Group($\"channel:{channel.Id}\")\n            .SendAsync(\"ChatMessageEdited\", message.ToDto(), cancellationToken)\n            .ConfigureAwait(false);","sourceCodeStart":5,"sourceCodeEnd":41,"githubUrl":"https://github.com/fullstackhero/dotnet-starter-kit/blob/3f2959e683e9f83f13e55e1678c9119f63c7e8e5/src/Modules/Chat/Modules.Chat/Features/v1/Messages/EditMessage/EditMessageCommandHandler.cs#L5-L41","documentation":"EditMessageCommandHandler throws UnauthorizedException(\"no current user\") when currentUser.GetUserId() yields Guid.Empty — no authenticated identity on the request. It guards the top of Handle before any lookup. Results in HTTP 401 via the shared exception middleware.","triggerScenarios":"PATCH/PUT edit-message call with missing/expired JWT; handler invoked from a job or test without an authenticated ICurrentUser; SignalR-triggered edit outside an HTTP auth context.","commonSituations":"Frontend fired the edit before login completed or after token expiry; endpoint missing [Authorize]; tests using a bare handler with default mocks (Guid.Empty).","solutions":["Attach a valid bearer token to the edit request; refresh expired tokens.","Verify [Authorize] and UseAuthentication/UseAuthorization are configured for the endpoint.","Ensure auth state is ready in the UI before enabling message editing.","In tests, mock ICurrentUser.GetUserId() to return a non-empty Guid."],"exampleFix":"// before\nvar userId = currentUser.GetUserId(); // Guid.Empty in test\n\n// after\ncurrentUserMock.GetCurrentUserId().Returns(Guid.NewGuid()); // authenticated test context","handlingStrategy":"try-catch","validationCode":"if (!auth.user?.id) return; // no identity — don't attempt the edit","typeGuard":"function hasUserId(u: unknown): u is { id: string } {\n  return typeof u === \"object\" && u !== null && typeof (u as { id?: unknown }).id === \"string\"\n    && (u as { id: string }).id !== \"00000000-0000-0000-0000-000000000000\";\n}","tryCatchPattern":"try {\n  await api.editMessage(messageId, body);\n} catch (e) {\n  if (e.status === 401) { await reauth(); }\n  else throw e;\n}","preventionTips":["Require a resolved auth state before enabling message editing UI.","Refresh tokens before they expire.","Mock ICurrentUser with a real Guid in handler tests.","Keep authentication middleware ordered before endpoints."],"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"}