Kareadita/Kavita · error · UnauthorizedAccessException

user-no-access-library-from-series

user-no-access-library-from-series

Error message

user-no-access-library-from-series

What it means

Thrown as UnauthorizedAccessException in GetSeriesDetail when the series' LibraryId is not in the set of libraries the user has access to (GetLibraryIdsForUserIdAsync). It is an authorization check: even if the series row exists, the user must belong to a library that contains it.

Source

Thrown at Kavita.Services/SeriesService.cs:532

            return false;
        }
    }

    /// <summary>
    /// This generates all the arrays needed by the Series Detail page in the UI. It is a specialized API for the unique layout constraints.
    /// </summary>
    /// <param name="seriesId"></param>
    /// <param name="userId"></param>
    /// <param name="ct"></param>
    /// <returns></returns>
    public async Task<SeriesDetailDto> GetSeriesDetail(int seriesId, int userId, CancellationToken ct = default)
    {
        var series = await unitOfWork.SeriesRepository.GetSeriesDtoByIdAsync(seriesId, userId, ct);
        if (series == null) throw new KavitaException(await localizationService.TranslateAsync(userId, "series-doesnt-exist"));

        var libraryIds = await unitOfWork.LibraryRepository.GetLibraryIdsForUserIdAsync(userId, ct: ct);
        if (!libraryIds.Contains(series.LibraryId))
            throw new UnauthorizedAccessException("user-no-access-library-from-series");

        var user = await unitOfWork.UserRepository.GetUserByIdAsync(userId, ct: ct);
        if (user!.AgeRestriction != AgeRating.NotApplicable)
        {
            var seriesMetadata = await unitOfWork.SeriesRepository.GetSeriesMetadataAsync(seriesId, ct);
            if (seriesMetadata!.AgeRating > user.AgeRestriction)
                throw new UnauthorizedAccessException("series-restricted-age-restriction");
        }


        var libraryType = await unitOfWork.LibraryRepository.GetLibraryTypeAsync(series.LibraryId, ct);
        var volumes = await unitOfWork.VolumeRepository.GetVolumesDtoAsync(seriesId, userId, ct: ct);
        var namingContext = await LocalizedNamingContext.CreateAsync(namingService, localizationService, userId, libraryType);
        var bookTreatment = libraryType is LibraryType.Book or LibraryType.LightNovel;

        // For books, the Name of the Volume is remapped to the actual name of the book, rather than Volume number.
        var processedVolumes = new List<VolumeDto>();
        foreach (var volume in volumes)

View on GitHub (pinned to 9c3e540000)

Solutions

  1. Grant the user access to the library containing the series (admin -> libraries -> members).
  2. Return 403 to the client and hide/redirect away from the inaccessible series.
  3. Filter series lists by the user's accessible libraries so such ids never surface.
  4. Audit library membership if access was expected.

Example fix

// before
var detail = await seriesService.GetSeriesDetail(seriesId, userId);

// after
var libs = await unitOfWork.LibraryRepository.GetLibraryIdsForUserIdAsync(userId, ct: ct);
var series = await unitOfWork.SeriesRepository.GetSeriesDtoByIdAsync(seriesId, userId, ct);
if (series is null || !libs.Contains(series.LibraryId))
    return Forbid();
var detail = await seriesService.GetSeriesDetail(seriesId, userId, ct);
Defensive patterns

Strategy: validation

Validate before calling

var libs = await unitOfWork.LibraryRepository.GetLibraryIdsForUserIdAsync(userId, ct: ct);
var series = await unitOfWork.SeriesRepository.GetSeriesDtoByIdAsync(seriesId, userId, ct);
if (series is null || !libs.Contains(series.LibraryId)) return Forbid();
var detail = await seriesService.GetSeriesDetail(seriesId, userId, ct);

Type guard

static bool UserCanAccessLibrary(int libraryId, IEnumerable<int> userLibs) => userLibs.Contains(libraryId);

Try / catch

try { return await seriesService.GetSeriesDetail(seriesId, userId, ct); }
catch (UnauthorizedAccessException) { return Forbid(); }

Prevention

When it happens

Trigger: GetSeriesDetail called for a series whose LibraryId is absent from the user's accessible library ids - user lacks the library role, or the series was moved to a restricted library.

Common situations: User's library access was revoked; admin moved the series to a library the user can't see; shared link opened by a user without that library; or age/library restriction policy excluded the library.

Related errors


AI-assisted analysis of Kareadita/Kavita@9c3e540000 (2026-08-13). Data as JSON: /api/errors/15de8911a300ee90. Report an issue: GitHub.