{"record":{"id":"446229cf1618e9a7","repo":"fullstackhero/dotnet-starter-kit","slug":"no-current-user-discoverchannelsqueryhandler","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/DiscoverChannels/DiscoverChannelsQueryHandler.cs","lineNumber":23,"sourceCode":"using FSH.Modules.Chat.Contracts.v1.Queries;\nusing FSH.Modules.Chat.Data;\nusing FSH.Modules.Chat.Domain;\nusing FSH.Modules.Chat.Features.v1.Internal;\nusing Mediator;\nusing Microsoft.EntityFrameworkCore;\n\nnamespace FSH.Modules.Chat.Features.v1.Channels.DiscoverChannels;\n\npublic sealed class DiscoverChannelsQueryHandler(\n    ChatDbContext db,\n    ICurrentUser currentUser)\n    : IQueryHandler<DiscoverChannelsQuery, ReadOnlyCollection<ChannelDto>>\n{\n    public async ValueTask<ReadOnlyCollection<ChannelDto>> Handle(DiscoverChannelsQuery q, CancellationToken cancellationToken)\n    {\n        ArgumentNullException.ThrowIfNull(q);\n        var userId = currentUser.GetUserId();\n        if (userId == Guid.Empty) throw new UnauthorizedException(\"no current user\");\n        var currentUserId = userId.ToString();\n\n        int page = Math.Max(1, q.Page);\n        int pageSize = Math.Clamp(q.PageSize, 1, 200);\n\n        var query = db.Channels.AsNoTracking()\n            .Where(c => c.Type == ChannelType.Channel\n                     && !c.IsPrivate\n                     && !c.Members.Any(m => m.UserId == currentUserId));\n\n        if (!string.IsNullOrWhiteSpace(q.Search))\n        {\n            var term = q.Search.Trim();\n            query = query.Where(c =>\n                EF.Functions.ILike(c.Name!, $\"%{term}%\")\n                || EF.Functions.ILike(c.Slug!, $\"%{term}%\"));\n        }\n","sourceCodeStart":5,"sourceCodeEnd":41,"githubUrl":"https://github.com/fullstackhero/dotnet-starter-kit/blob/3f2959e683e9f83f13e55e1678c9119f63c7e8e5/src/Modules/Chat/Modules.Chat/Features/v1/Channels/DiscoverChannels/DiscoverChannelsQueryHandler.cs#L5-L41","documentation":"DiscoverChannelsQueryHandler throws UnauthorizedException when currentUser.GetUserId() returns Guid.Empty — discovery is scoped to the calling user (it excludes their existing memberships), so an anonymous request cannot be served.","triggerScenarios":"GET channel discovery without a valid JWT, expired token, or calling the query handler directly in tests with an unauthenticated ICurrentUser.","commonSituations":"Token expired while browsing; missing Authorization header on a 'public-looking' endpoint the developer assumed was anonymous; tests forgetting auth setup.","solutions":["Attach a valid bearer token to the discovery request","Implement 401 handling: refresh token and retry once","In tests, mock ICurrentUser.GetUserId() to return a real Guid"],"exampleFix":"// before\nconst res = await fetch('/api/v1/channels/discover'); // 401 no current user\n// after\nconst res = await apiFetch('/api/v1/channels/discover'); // apiFetch attaches JWT and handles 401 refresh","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 discover(); }\ncatch (UnauthorizedException) { /* refresh token and retry once */ }","preventionTips":["Never call discovery endpoints anonymously","Centralize auth in apiFetch so JWT is always attached","Retry once after token refresh on 401"],"tags":["auth","unauthorized","chat","query"],"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"}