{"record":{"id":"9a4e1d9c2069ab2a","repo":"Kareadita/Kavita","slug":"no-library-access","errorCode":null,"errorMessage":"no-library-access","messagePattern":"no-library-access","errorType":"exception","errorClass":"OpdsException","httpStatus":null,"severity":"error","filePath":"Kavita.Services/OpdsService.cs","lineNumber":470,"sourceCode":"        foreach (var seriesDto in series)\n        {\n            feed.Entries.Add(CreateSeries(seriesDto, seriesMetadatas.First(s => s.SeriesId == seriesDto.Id), apiKey, prefix, baseUrl));\n        }\n\n        return feed;\n    }\n\n    public async Task<Feed> GetSeriesFromLibrary(OpdsItemsFromEntityIdRequest request, CancellationToken ct = default)\n    {\n        var userId = UnpackRequest(request, out var apiKey, out var prefix, out var baseUrl);\n        var libraryId = request.EntityId;\n\n        var library = (await unitOfWork.LibraryRepository.GetLibrariesForUserIdAsync(userId, ct))\n            .SingleOrDefault(l => l.Id == libraryId);\n\n        if (library == null)\n        {\n            throw new OpdsException(await localizationService.TranslateAsync(userId, \"no-library-access\"));\n        }\n\n        var filter = new SeriesFilterV2Dto\n        {\n            Statements = [\n                new SeriesFilterStatementDto\n                {\n                    Comparison = FilterComparison.Equal,\n                    Field = SeriesFilterField.Libraries,\n                    Value = libraryId + string.Empty\n                }\n            ]\n        };\n\n        var series = await unitOfWork.SeriesRepository.GetSeriesDtoForLibraryIdAsync(userId, GetUserParams(request.PageNumber), filter, ct: ct);\n        var seriesMetadatas = await unitOfWork.SeriesRepository.GetSeriesMetadataForIdsAsync(series.Select(s => s.Id), ct);\n\n        var feed = CreateFeed(library.Name, $\"{apiKey}/libraries/{libraryId}\", apiKey, prefix);","sourceCodeStart":452,"sourceCodeEnd":488,"githubUrl":"https://github.com/Kareadita/Kavita/blob/9c3e5400007f8a0282f7d883f2ad5e71716e514d/Kavita.Services/OpdsService.cs#L452-L488","documentation":"Thrown by OpdsService.GetSeriesFromLibrary when the requesting user has no access to the specified library. The service loads all libraries the user can access (GetLibrariesForUserIdAsync) and uses SingleOrDefault to find the one matching libraryId. If none matches — meaning the user has no library-access role that includes this library — the throw fires. This is an authorization check, not a not-found check: even if the library exists in the system, a user without access gets the same error.","triggerScenarios":"An OPDS client requests {apiKey}/libraries/{libraryId}. The user's library-access grants do not include this libraryId. This can happen if an admin revoked library access, if the user was never granted access, or if the libraryId is invalid.","commonSituations":"An admin changed the user's role, removing library access. A new user tries to access a library they haven't been granted. The OPDS reader cached a library URL from a session when access was available but it was since revoked. The libraryId was deleted or renumbered.","solutions":["Have an admin verify the user's role includes access to the requested library (Admin Panel -> Users -> Roles -> Library Access).","Refresh the library listing in the OPDS client; only libraries the user can access will appear.","If the library was deleted, the OPDS client should remove the stale bookmark.","Confirm the correct API key is being used — a different user's key grants different library access.","Check the LibraryAppUser join table or admin panel to confirm the user-library grant exists."],"exampleFix":"// No code fix — authorization issue.\n// Admin Panel -> Users -> select user -> Edit Roles\n//   -> check the library under 'Library Access'\n// Save, then user re-authenticates in OPDS client.\n\n// In an OPDS client, only use library IDs from the root feed,\n// which already filters by the user's access.","handlingStrategy":"validation","validationCode":"// Before requesting a library feed, verify the user has access:\n// var userLibraries = await unitOfWork.LibraryRepository.GetLibrariesForUserIdAsync(userId, ct);\n// if (!userLibraries.Any(l => l.Id == libraryId))\n// {\n//     ShowUser(\"You do not have access to this library.\");\n//     return;\n// }\n// var feed = await opdsService.GetSeriesFromLibrary(request, ct);","typeGuard":null,"tryCatchPattern":"// try { var feed = await opdsService.GetSeriesFromLibrary(request, ct); }\n// catch (OpdsException ex) when (ex.Message.Contains(\"no-library-access\"))\n// {\n//     // Library access was revoked or never granted.\n//     // Refresh the root feed to get the user's valid libraries.\n//     await RefreshLibraryList();\n//     return BadRequest(ex.Message);\n// }","preventionTips":["Admins should verify library-access roles before users connect via OPDS.","In OPDS clients, only use library IDs from the user's root feed.","After revoking library access, inform affected users to refresh their OPDS reader.","Use the correct API key for the user's access level.","Handle 'no-library-access' by refreshing the library list in the OPDS client."],"tags":["opds","library","access-control","authorization","feed"],"backgroundTag":null,"analyzedSha":"9c3e5400007f8a0282f7d883f2ad5e71716e514d","analyzedAt":"2026-08-13T19:06:05.897Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}