{"record":{"id":"928641cbc51c0d55","repo":"fullstackhero/dotnet-starter-kit","slug":"no-current-user-listchannelmessagesqueryhandler","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/ListChannelMessages/ListChannelMessagesQueryHandler.cs","lineNumber":25,"sourceCode":"using FSH.Modules.Chat.Features.v1.Internal;\nusing Mediator;\nusing Microsoft.EntityFrameworkCore;\n\nnamespace FSH.Modules.Chat.Features.v1.Messages.ListChannelMessages;\n\npublic sealed class ListChannelMessagesQueryHandler(\n    ChatDbContext db,\n    ICurrentUser currentUser,\n    IMediator mediator)\n    : IQueryHandler<ListChannelMessagesQuery, ReadOnlyCollection<MessageDto>>\n{\n    public async ValueTask<ReadOnlyCollection<MessageDto>> Handle(\n        ListChannelMessagesQuery query,\n        CancellationToken cancellationToken)\n    {\n        ArgumentNullException.ThrowIfNull(query);\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 == query.ChannelId, cancellationToken)\n            .ConfigureAwait(false)\n            ?? throw new NotFoundException(\"Channel not found.\");\n        channel.RequireMember(currentUserId);\n\n        // Top-level only (no thread replies). Guid v7 monotonic → Id desc = time desc.\n        IQueryable<Domain.Message> q = db.Messages\n            .Where(m => m.ChannelId == query.ChannelId && m.ParentMessageId == null);\n\n        if (query.Before is { } beforeId)\n        {\n            q = q.Where(m => m.Id.CompareTo(beforeId) < 0);\n        }\n\n        var rows = await q\n            .OrderByDescending(m => m.Id)","sourceCodeStart":7,"sourceCodeEnd":43,"githubUrl":"https://github.com/fullstackhero/dotnet-starter-kit/blob/3f2959e683e9f83f13e55e1678c9119f63c7e8e5/src/Modules/Chat/Modules.Chat/Features/v1/Messages/ListChannelMessages/ListChannelMessagesQueryHandler.cs#L7-L43","documentation":"ListChannelMessages throws UnauthorizedException('no current user') when currentUser.GetUserId() returns Guid.Empty, i.e. no authenticated identity on the request. Maps to HTTP 401 and is thrown before the channel lookup to avoid leaking channel existence to anonymous callers.","triggerScenarios":"Listing channel messages without a valid JWT; missing, expired, or otherwise unauthenticated request context.","commonSituations":"SignalR/SSE or background contexts calling the handler without user propagation, expired tokens, or misconfigured auth middleware in tests.","solutions":["Obtain/refresh a valid access token and send it in the Authorization header","Fix token refresh logic so expired tokens are renewed before calls","Ensure test harnesses/background jobs seed an ICurrentUser instead of calling handlers raw","Validate JWT bearer options (issuer/audience) so tokens authenticate"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"if (!auth.isAuthenticated()) throw new Error('login required before listing messages');","typeGuard":"function isAuthed(ctx: { UserId?: string }): boolean { return typeof ctx.UserId === 'string' && ctx.UserId !== '00000000-0000-0000-0000-000000000000'; }","tryCatchPattern":"try { return await listChannelMessages(channelId); }\ncatch (e) { if (isUnauthorized(e)) { await auth.refresh(); return listChannelMessages(channelId); } throw e; }","preventionTips":["Central token refresh before calls","Attach bearer token globally via interceptor","Seed a current user in integration tests","Treat 401 as re-auth signal, not a bug"],"tags":["auth","chat","jwt"],"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"}