{"record":{"id":"b069e139a0eec7fe","repo":"Kareadita/Kavita","slug":"not-authenticated","errorCode":null,"errorMessage":"not-authenticated","messagePattern":"not-authenticated","errorType":"exception","errorClass":"KavitaException","httpStatus":400,"severity":"error","filePath":"Kavita.Services/StreamService.cs","lineNumber":290,"sourceCode":"            wantedPosition = list.IndexOf(itemAtWantedPosition);\n        }\n\n        OrderableHelper.ReorderItems(list, stream.Id, wantedPosition);\n        user.SideNavStreams = list;\n\n        unitOfWork.UserRepository.Update(user);\n        await unitOfWork.CommitAsync(ct);\n        if (!stream.Visible) return;\n        await eventHub.SendMessageToAsync(MessageFactory.SideNavUpdate, MessageFactory.SideNavUpdateEvent(userId),\n            userId, ct);\n    }\n\n    public async Task<ExternalSourceDto> CreateExternalSource(int userId, ExternalSourceDto dto,\n        CancellationToken ct = default)\n    {\n        var user = await unitOfWork.UserRepository.GetUserByIdAsync(userId,\n            AppUserIncludes.ExternalSources, ct);\n        if (user == null) throw new KavitaException(\"not-authenticated\");\n\n        if (user.ExternalSources.Any(s => s.Host == dto.Host))\n        {\n            throw new KavitaException(\"external-source-already-exists\");\n        }\n\n        if (string.IsNullOrEmpty(dto.Name)) throw new KavitaException(\"external-source-required\");\n        if (!UrlHelper.StartsWithHttpOrHttps(dto.Host)) throw new KavitaException(\"external-source-host-format\");\n\n\n        var newSource = new AppUserExternalSource()\n        {\n            Name = dto.Name,\n            Host = UrlHelper.EnsureEndsWithSlash(UrlHelper.EnsureStartsWithHttpOrHttps(dto.Host)),\n            ApiKey = dto.ApiKey\n        };\n        user.ExternalSources.Add(newSource);\n","sourceCodeStart":272,"sourceCodeEnd":308,"githubUrl":"https://github.com/Kareadita/Kavita/blob/9c3e5400007f8a0282f7d883f2ad5e71716e514d/Kavita.Services/StreamService.cs#L272-L308","documentation":"Thrown by StreamService.CreateExternalSource when GetUserByIdAsync(userId, AppUserIncludes.ExternalSources) returns null. Despite the key 'not-authenticated', the request was authenticated — the real condition is that the principal's UserId claim does not resolve to an AppUser row (deleted account, stale/invalid token, corrupted claim). Unlike the surrounding throws this one uses a hard-coded string rather than localizationService.TranslateAsync. As a plain KavitaException it surfaces as HTTP 500.","triggerScenarios":"POST /api/streams/create-external-source issued with a valid JWT whose UserId claim points to a user that no longer exists in the database (admin deleted the account mid-session, or the claim is stale).","commonSituations":"Account was deleted or the user was migrated to a new id while the old token was still accepted; a test/migration inserted an orphaned JWT; UserId resolution in BaseApiController returned a value not present in the users table.","solutions":["Have the client log out and re-authenticate to obtain a fresh token, then retry.","On the server, verify the UserId claim maps to a live user at the auth boundary; if not, return 401 instead of reaching the service.","Ensure user-merge/delete flows revoke outstanding tokens so stale ids cannot reach this code."],"exampleFix":"// before\npublic async Task<ActionResult<ExternalSourceDto>> CreateExternalSource(ExternalSourceDto dto)\n    => Ok(await streamService.CreateExternalSource(UserId, dto));\n// after\npublic async Task<ActionResult<ExternalSourceDto>> CreateExternalSource(ExternalSourceDto dto)\n{\n    if (UserId <= 0) return Unauthorized();\n    return Ok(await streamService.CreateExternalSource(UserId, dto));\n}","handlingStrategy":"validation","validationCode":"function hasValidUserId(userId: number | null | undefined): boolean {\n  return typeof userId === 'number' && userId > 0;\n}","typeGuard":null,"tryCatchPattern":"// on 'not-authenticated' from create-external-source: clear local session and re-authenticate\nthis.auth.logout(); this.router.navigate(['/login']);","preventionTips":["Treat this error as a session invalidation: log out and obtain a fresh token.","Ensure account deletion/merge flows revoke outstanding JWTs.","Do not cache a UserId that cannot be revalidated against /api/users."],"tags":["authentication","user","validation"],"backgroundTag":null,"analyzedSha":"9c3e5400007f8a0282f7d883f2ad5e71716e514d","analyzedAt":"2026-08-13T19:06:05.897Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}