Kareadita/Kavita · error · KavitaException
not-authenticated
Error message
not-authenticated
What it means
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.
Source
Thrown at Kavita.Services/StreamService.cs:290
wantedPosition = list.IndexOf(itemAtWantedPosition);
}
OrderableHelper.ReorderItems(list, stream.Id, wantedPosition);
user.SideNavStreams = list;
unitOfWork.UserRepository.Update(user);
await unitOfWork.CommitAsync(ct);
if (!stream.Visible) return;
await eventHub.SendMessageToAsync(MessageFactory.SideNavUpdate, MessageFactory.SideNavUpdateEvent(userId),
userId, ct);
}
public async Task<ExternalSourceDto> CreateExternalSource(int userId, ExternalSourceDto dto,
CancellationToken ct = default)
{
var user = await unitOfWork.UserRepository.GetUserByIdAsync(userId,
AppUserIncludes.ExternalSources, ct);
if (user == null) throw new KavitaException("not-authenticated");
if (user.ExternalSources.Any(s => s.Host == dto.Host))
{
throw new KavitaException("external-source-already-exists");
}
if (string.IsNullOrEmpty(dto.Name)) throw new KavitaException("external-source-required");
if (!UrlHelper.StartsWithHttpOrHttps(dto.Host)) throw new KavitaException("external-source-host-format");
var newSource = new AppUserExternalSource()
{
Name = dto.Name,
Host = UrlHelper.EnsureEndsWithSlash(UrlHelper.EnsureStartsWithHttpOrHttps(dto.Host)),
ApiKey = dto.ApiKey
};
user.ExternalSources.Add(newSource);
View on GitHub (pinned to 9c3e540000)
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.
Example fix
// before
public async Task<ActionResult<ExternalSourceDto>> CreateExternalSource(ExternalSourceDto dto)
=> Ok(await streamService.CreateExternalSource(UserId, dto));
// after
public async Task<ActionResult<ExternalSourceDto>> CreateExternalSource(ExternalSourceDto dto)
{
if (UserId <= 0) return Unauthorized();
return Ok(await streamService.CreateExternalSource(UserId, dto));
} Defensive patterns
Strategy: validation
Validate before calling
function hasValidUserId(userId: number | null | undefined): boolean {
return typeof userId === 'number' && userId > 0;
} Try / catch
// on 'not-authenticated' from create-external-source: clear local session and re-authenticate this.auth.logout(); this.router.navigate(['/login']);
Prevention
- 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.
When it happens
Trigger: 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).
Common situations: 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.
Understand the failure class
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- oidc-invalid-authority
- no-user
- User is not authenticated
- {comparison} is not applicable for {fieldName}
- Name must be set
AI-assisted analysis of Kareadita/Kavita@9c3e540000 (2026-08-13).
Data as JSON: /api/errors/b069e139a0eec7fe.
Report an issue: GitHub.