Skip to content

fix(security): add access control annotations to various controllers#312

Merged
balazs-szucs merged 3 commits intogrimmory-tools:developfrom
balazs-szucs:security-stuff
Mar 31, 2026
Merged

fix(security): add access control annotations to various controllers#312
balazs-szucs merged 3 commits intogrimmory-tools:developfrom
balazs-szucs:security-stuff

Conversation

@balazs-szucs
Copy link
Copy Markdown
Member

@balazs-szucs balazs-szucs commented Mar 31, 2026

Description

Linked Issue: Fixes #

Changes

This pull request enhances the security of several controller endpoints by adding method-level authorization checks using Spring Security annotations. The updates ensure that sensitive operations, such as updating settings, accessing book content, synchronizing with Kobo, and managing tasks, are only accessible to users with the appropriate permissions. Additionally, book-related endpoints now consistently enforce access checks to protect user data.

Authorization and Access Control Enhancements:

  • Added @PreAuthorize checks to restrict the following endpoints to users with specific roles or permissions:
    • Updating application settings now requires admin privileges in AppSettingController.
    • Bulk cover image upload and retrieval in BookCoverController now requires metadata edit or admin permissions.
    • Replacing book content in BookController now requires metadata edit or admin permissions.
    • Accessing Kobo sync settings in KoboSettingsController now requires Kobo sync or admin permissions.
    • Starting background tasks in TaskController now requires task manager or admin permissions.

Book Access Validation:

  • Introduced or enforced the @CheckBookAccess annotation to ensure users can only access book data they are authorized for in the following endpoints:
    • Retrieving ComicInfo metadata and file metadata in BookController
    • Listing pages and getting book info in PdfReaderController

Summary by CodeRabbit

  • Bug Fixes
    • Strengthened API security by adding/aligning authorization checks across settings, book metadata/content, cover images, reader endpoints, Kobo sync, and task-start endpoints—access now requires appropriate permissions.
  • Chores
    • Reduced sensitive data in debug/warning logs and standardized error responses to generic messages for improved privacy and consistency.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 31, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 138e1b8a-d78e-4a44-b891-6b46138e81ef

📥 Commits

Reviewing files that changed from the base of the PR and between c593c01 and d7efc64.

📒 Files selected for processing (6)
  • booklore-api/src/main/java/org/booklore/config/security/aspect/BookAccessAspect.java
  • booklore-api/src/main/java/org/booklore/config/security/aspect/LibraryAccessAspect.java
  • booklore-api/src/main/java/org/booklore/config/security/filter/KoboAuthFilter.java
  • booklore-api/src/main/java/org/booklore/config/security/filter/KoreaderAuthFilter.java
  • booklore-api/src/main/java/org/booklore/controller/AuthenticationController.java
  • booklore-api/src/main/java/org/booklore/exception/GlobalExceptionHandler.java
✅ Files skipped from review due to trivial changes (1)
  • booklore-api/src/main/java/org/booklore/exception/GlobalExceptionHandler.java
📜 Recent review details
🧰 Additional context used
📓 Path-based instructions (1)
booklore-api/src/**/*.java

📄 CodeRabbit inference engine (AGENTS.md)

booklore-api/src/**/*.java: Use 4-space indentation in Java files and match surrounding Java style
Prefer constructor injection via Lombok patterns already used in the codebase. Do not introduce @Autowired field injection
Use MapStruct for entity/DTO mapping

Files:

  • booklore-api/src/main/java/org/booklore/config/security/aspect/LibraryAccessAspect.java
  • booklore-api/src/main/java/org/booklore/config/security/aspect/BookAccessAspect.java
  • booklore-api/src/main/java/org/booklore/config/security/filter/KoreaderAuthFilter.java
  • booklore-api/src/main/java/org/booklore/controller/AuthenticationController.java
  • booklore-api/src/main/java/org/booklore/config/security/filter/KoboAuthFilter.java
🧠 Learnings (5)
📓 Common learnings
Learnt from: imnotjames
Repo: grimmory-tools/grimmory PR: 195
File: booklore-api/src/main/java/org/booklore/config/logging/RequestLoggingFilterConfig.java:15-19
Timestamp: 2026-03-25T21:02:57.527Z
Learning: In `booklore-api/src/main/java/org/booklore/config/logging/RequestLoggingFilterConfig.java` and `booklore-api/src/main/java/org/booklore/config/logging/filter/RequestLoggingFilter.java`, logging all request headers and payloads at DEBUG level is intentional and accepted behavior in this project. The previous `LoggingFilter` also logged all headers. No header redaction or payload scrubbing is required — the DEBUG log level is considered sufficient access control. The new filter is not profile-gated (unlike the old `Profile("dev")` filter) but relies on `logger.isDebugEnabled()` for gating.
📚 Learning: 2026-03-25T19:09:09.638Z
Learnt from: imnotjames
Repo: grimmory-tools/grimmory PR: 189
File: booklore-api/src/main/java/org/booklore/service/kobo/KoboLibrarySyncService.java:113-116
Timestamp: 2026-03-25T19:09:09.638Z
Learning: In `booklore-api/src/main/java/org/booklore/service/kobo/KoboLibrarySyncService.java`, when deciding whether to forward the `Authorization` header to the upstream Kobo server proxy on the `/v1/library/sync` endpoint, a simple null check (`request.getHeader(HttpHeaders.AUTHORIZATION) != null`) is the correct guard. The header is either absent or present — if present it may or may not be a valid token, but validity is determined by the upstream Kobo server, not by Grimmory. Blank/whitespace intermediate states do not occur in practice, and pre-validating the token value before forwarding is not the responsibility of this service.

Applied to files:

  • booklore-api/src/main/java/org/booklore/config/security/aspect/LibraryAccessAspect.java
  • booklore-api/src/main/java/org/booklore/config/security/filter/KoreaderAuthFilter.java
  • booklore-api/src/main/java/org/booklore/config/security/filter/KoboAuthFilter.java
📚 Learning: 2026-03-25T21:02:57.527Z
Learnt from: imnotjames
Repo: grimmory-tools/grimmory PR: 195
File: booklore-api/src/main/java/org/booklore/config/logging/RequestLoggingFilterConfig.java:15-19
Timestamp: 2026-03-25T21:02:57.527Z
Learning: In `booklore-api/src/main/java/org/booklore/config/logging/RequestLoggingFilterConfig.java` and `booklore-api/src/main/java/org/booklore/config/logging/filter/RequestLoggingFilter.java`, logging all request headers and payloads at DEBUG level is intentional and accepted behavior in this project. The previous `LoggingFilter` also logged all headers. No header redaction or payload scrubbing is required — the DEBUG log level is considered sufficient access control. The new filter is not profile-gated (unlike the old `Profile("dev")` filter) but relies on `logger.isDebugEnabled()` for gating.

Applied to files:

  • booklore-api/src/main/java/org/booklore/config/security/filter/KoreaderAuthFilter.java
  • booklore-api/src/main/java/org/booklore/controller/AuthenticationController.java
  • booklore-api/src/main/java/org/booklore/config/security/filter/KoboAuthFilter.java
📚 Learning: 2026-03-27T15:06:25.913Z
Learnt from: imnotjames
Repo: grimmory-tools/grimmory PR: 217
File: booklore-api/src/main/java/org/booklore/service/kobo/KoboLibrarySyncService.java:151-153
Timestamp: 2026-03-27T15:06:25.913Z
Learning: In `booklore-api/src/main/java/org/booklore/service/kobo/KoboLibrarySyncService.java`, the try/catch around `koboServerProxy.proxyCurrentRequest()` in `syncLibrary()` is intentionally scoped only to handle a missing/failed proxy response (null). Once a response is successfully obtained, its body and headers (including the upstream sync token decoded via `tokenGenerator.fromBase64()`) are processed without additional defensive wrapping. This is a deliberate design decision: other parts of the response are equally unguarded, and the goal is to handle proxy unavailability gracefully — not to catch every possible downstream processing error.

Applied to files:

  • booklore-api/src/main/java/org/booklore/config/security/filter/KoboAuthFilter.java
📚 Learning: 2026-03-26T02:20:27.862Z
Learnt from: imnotjames
Repo: grimmory-tools/grimmory PR: 198
File: booklore-api/src/main/java/org/booklore/controller/KoboController.java:100-103
Timestamp: 2026-03-26T02:20:27.862Z
Learning: In `booklore-api/src/main/java/org/booklore/controller/KoboController.java`, the Kobo reader device already makes direct outbound connections to Kobo's CDN/servers for other requests (confirmed by imnotjames via testing). Therefore, returning a `307 TEMPORARY_REDIRECT` to `cdn.kobo.com` for non-`BL-` book cover thumbnails (instead of server-side proxying) does NOT introduce a new privacy or behavioral regression — the Kobo device's IP and User-Agent are already exposed to Kobo through other direct requests.

Applied to files:

  • booklore-api/src/main/java/org/booklore/config/security/filter/KoboAuthFilter.java
🔇 Additional comments (5)
booklore-api/src/main/java/org/booklore/controller/AuthenticationController.java (1)

85-93: Good log hardening for remote auth flow.

Line 85 and Line 93 now avoid printing user-identifying values while keeping enough signal for debugging.

booklore-api/src/main/java/org/booklore/config/security/filter/KoboAuthFilter.java (1)

56-66: Nice improvement: warning logs no longer leak KOBO token context.

The updated messages on Line 56 and Line 65 keep behavior unchanged while reducing sensitive log content.

booklore-api/src/main/java/org/booklore/config/security/aspect/BookAccessAspect.java (1)

50-52: Good defensive check for missing authentication.

Line 50-52 correctly fails fast when no authenticated user is available.

booklore-api/src/main/java/org/booklore/config/security/aspect/LibraryAccessAspect.java (1)

42-44: Solid auth-null guard addition.

Line 42-44 closes a null-auth edge case before role/library checks execute.

booklore-api/src/main/java/org/booklore/config/security/filter/KoreaderAuthFilter.java (1)

61-63: Good reduction of sensitive auth-failure logging.

Line 61 and Line 63 keep operational signal while removing user-specific details from warnings.


📝 Walkthrough

Walkthrough

Added method-level access controls and authentication checks across controllers and security layers; redacted sensitive values from several auth-related logs; standardized exception handler messages; and added null-checks to book/library access aspects. No public method signatures or request/response shapes were changed.

Changes

Cohort / File(s) Summary
Reader Controllers
booklore-api/src/main/java/org/booklore/controller/CbxReaderController.java, booklore-api/src/main/java/org/booklore/controller/PdfReaderController.java
Added @CheckBookAccess(bookIdParam = "bookId") to multiple reader endpoints to enforce book-level access checks.
Book & Cover Endpoints
booklore-api/src/main/java/org/booklore/controller/BookController.java, booklore-api/src/main/java/org/booklore/controller/BookCoverController.java
Added @CheckBookAccess to read endpoints and tightened write/read access with @PreAuthorize("@securityUtil.canEditMetadata() or @securityUtil.isAdmin()") on content replacement and cover-image endpoints.
Settings & Tasks
booklore-api/src/main/java/org/booklore/controller/AppSettingController.java, .../KoboSettingsController.java, .../TaskController.java
Added @PreAuthorize guards: settings update restricted to admin; Kobo settings GET requires Kobo-sync or admin; task start requires task-manager or admin.
Security Aspects
booklore-api/src/main/java/org/booklore/config/security/aspect/BookAccessAspect.java, .../LibraryAccessAspect.java
Added explicit null-checks for authenticated user and throw ApiError.FORBIDDEN with "Authentication required." when unauthenticated before permission checks.
Auth Filters & Controller Logging
booklore-api/src/main/java/org/booklore/config/security/filter/KoboAuthFilter.java, .../KoreaderAuthFilter.java, .../controller/AuthenticationController.java
Removed sensitive values from warning/debug logs (no longer interpolating tokens, usernames, emails); retained control flow and authentication behavior.
Global Exception Handling
booklore-api/src/main/java/org/booklore/exception/GlobalExceptionHandler.java
Replaced propagated exception messages with generic fixed messages for several handlers (AccessDenied, NoResourceFound, IllegalState, UnsupportedOperation) while keeping status codes and response shapes unchanged.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 I hopped through routes and checked each door,

"Are you allowed?" — I asked, and asked once more.
Logs trimmed their tails, errors now speak plain,
Secure and tidy, I hop off down the lane. 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The PR title follows the conventional commit format with type 'fix', scope 'security', and a descriptive subject that accurately summarizes the main changes of adding access control annotations.
Description check ✅ Passed The PR description includes all required template sections (Description and Changes) with comprehensive details about the authorization enhancements, specific endpoints modified, and the rationale for changes.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
✨ Simplify code
  • Create PR with simplified code

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@booklore-api/src/main/java/org/booklore/controller/BookCoverController.java`:
- Around line 148-152: The endpoint in BookCoverController.getImages exposes a
path with {bookId} but the method signature lacks a `@PathVariable` Long bookId
and is missing the consistent access check; either remove {bookId} from the
`@PostMapping` path or add `@PathVariable` Long bookId and annotate the method with
`@CheckBookAccess`(bookIdParam = "bookId") so access is validated before calling
duckDuckGoCoverService.getCovers(request) (CoverFetchRequest remains the body
parameter); update the method signature accordingly to match other methods like
uploadCoverFromFile/uploadCoverFromUrl.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: e9792b05-606b-4432-90f0-cf7a72fd1019

📥 Commits

Reviewing files that changed from the base of the PR and between a2fc326 and c593c01.

📒 Files selected for processing (7)
  • booklore-api/src/main/java/org/booklore/controller/AppSettingController.java
  • booklore-api/src/main/java/org/booklore/controller/BookController.java
  • booklore-api/src/main/java/org/booklore/controller/BookCoverController.java
  • booklore-api/src/main/java/org/booklore/controller/CbxReaderController.java
  • booklore-api/src/main/java/org/booklore/controller/KoboSettingsController.java
  • booklore-api/src/main/java/org/booklore/controller/PdfReaderController.java
  • booklore-api/src/main/java/org/booklore/controller/TaskController.java
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Analyze (actions)
  • GitHub Check: Analyze (java-kotlin)
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: Packaging Smoke Test
🧰 Additional context used
📓 Path-based instructions (1)
booklore-api/src/**/*.java

📄 CodeRabbit inference engine (AGENTS.md)

booklore-api/src/**/*.java: Use 4-space indentation in Java files and match surrounding Java style
Prefer constructor injection via Lombok patterns already used in the codebase. Do not introduce @Autowired field injection
Use MapStruct for entity/DTO mapping

Files:

  • booklore-api/src/main/java/org/booklore/controller/AppSettingController.java
  • booklore-api/src/main/java/org/booklore/controller/BookController.java
  • booklore-api/src/main/java/org/booklore/controller/TaskController.java
  • booklore-api/src/main/java/org/booklore/controller/BookCoverController.java
  • booklore-api/src/main/java/org/booklore/controller/CbxReaderController.java
  • booklore-api/src/main/java/org/booklore/controller/PdfReaderController.java
  • booklore-api/src/main/java/org/booklore/controller/KoboSettingsController.java
🧠 Learnings (3)
📓 Common learnings
Learnt from: imnotjames
Repo: grimmory-tools/grimmory PR: 189
File: booklore-api/src/main/java/org/booklore/service/kobo/KoboLibrarySyncService.java:113-116
Timestamp: 2026-03-25T19:09:09.638Z
Learning: In `booklore-api/src/main/java/org/booklore/service/kobo/KoboLibrarySyncService.java`, when deciding whether to forward the `Authorization` header to the upstream Kobo server proxy on the `/v1/library/sync` endpoint, a simple null check (`request.getHeader(HttpHeaders.AUTHORIZATION) != null`) is the correct guard. The header is either absent or present — if present it may or may not be a valid token, but validity is determined by the upstream Kobo server, not by Grimmory. Blank/whitespace intermediate states do not occur in practice, and pre-validating the token value before forwarding is not the responsibility of this service.
📚 Learning: 2026-03-26T02:20:27.862Z
Learnt from: imnotjames
Repo: grimmory-tools/grimmory PR: 198
File: booklore-api/src/main/java/org/booklore/controller/KoboController.java:100-103
Timestamp: 2026-03-26T02:20:27.862Z
Learning: In `booklore-api/src/main/java/org/booklore/controller/KoboController.java`, the Kobo reader device already makes direct outbound connections to Kobo's CDN/servers for other requests (confirmed by imnotjames via testing). Therefore, returning a `307 TEMPORARY_REDIRECT` to `cdn.kobo.com` for non-`BL-` book cover thumbnails (instead of server-side proxying) does NOT introduce a new privacy or behavioral regression — the Kobo device's IP and User-Agent are already exposed to Kobo through other direct requests.

Applied to files:

  • booklore-api/src/main/java/org/booklore/controller/BookController.java
  • booklore-api/src/main/java/org/booklore/controller/BookCoverController.java
  • booklore-api/src/main/java/org/booklore/controller/CbxReaderController.java
  • booklore-api/src/main/java/org/booklore/controller/PdfReaderController.java
📚 Learning: 2026-03-25T19:09:09.638Z
Learnt from: imnotjames
Repo: grimmory-tools/grimmory PR: 189
File: booklore-api/src/main/java/org/booklore/service/kobo/KoboLibrarySyncService.java:113-116
Timestamp: 2026-03-25T19:09:09.638Z
Learning: In `booklore-api/src/main/java/org/booklore/service/kobo/KoboLibrarySyncService.java`, when deciding whether to forward the `Authorization` header to the upstream Kobo server proxy on the `/v1/library/sync` endpoint, a simple null check (`request.getHeader(HttpHeaders.AUTHORIZATION) != null`) is the correct guard. The header is either absent or present — if present it may or may not be a valid token, but validity is determined by the upstream Kobo server, not by Grimmory. Blank/whitespace intermediate states do not occur in practice, and pre-validating the token value before forwarding is not the responsibility of this service.

Applied to files:

  • booklore-api/src/main/java/org/booklore/controller/KoboSettingsController.java
🔇 Additional comments (8)
booklore-api/src/main/java/org/booklore/controller/AppSettingController.java (1)

45-52: LGTM!

The @PreAuthorize("@securityUtil.isAdmin()") annotation appropriately restricts application settings modification to administrators, consistent with the existing testOidcConnection endpoint's security pattern.

booklore-api/src/main/java/org/booklore/controller/KoboSettingsController.java (1)

26-31: LGTM!

The @PreAuthorize annotation aligns getSettings() with the existing authorization pattern on createOrUpdateToken() and updateSettings(), ensuring consistent access control across all Kobo settings endpoints.

booklore-api/src/main/java/org/booklore/controller/TaskController.java (1)

39-47: LGTM!

Adding @PreAuthorize("@securityUtil.canAccessTaskManager() or @securityUtil.isAdmin()") to startTask() closes the authorization gap and aligns this endpoint with the other task management endpoints in the controller.

booklore-api/src/main/java/org/booklore/controller/CbxReaderController.java (1)

3-3: LGTM!

The @CheckBookAccess(bookIdParam = "bookId") annotations properly secure all three CBX reader endpoints, ensuring users can only access book pages and metadata for books they have library-level access to. The implementation is consistent with the parallel changes in PdfReaderController.

Also applies to: 26-31, 36-42, 46-52

booklore-api/src/main/java/org/booklore/controller/PdfReaderController.java (1)

3-3: LGTM!

The @CheckBookAccess(bookIdParam = "bookId") annotations properly secure both PDF reader endpoints, matching the pattern applied to CbxReaderController and ensuring consistent access control across all reader controllers.

Also applies to: 25-31, 35-41

booklore-api/src/main/java/org/booklore/controller/BookController.java (3)

127-132: LGTM!

Adding @CheckBookAccess to getComicInfoMetadata() ensures users can only retrieve ComicInfo metadata for books in their assigned libraries, closing an access control gap on this read endpoint.


136-141: LGTM!

Adding @CheckBookAccess to getFileMetadata() appropriately restricts file metadata extraction to books the user has access to.


163-172: LGTM!

The combination of @PreAuthorize("@securityUtil.canEditMetadata() or @securityUtil.isAdmin()") with the existing @CheckBookAccess provides proper defense-in-depth: Spring Security's @PreAuthorize validates permission to edit metadata first, then BookAccessAspect verifies the user has access to the specific book. This is appropriate for a content-replacement operation.

@balazs-szucs balazs-szucs merged commit e663422 into grimmory-tools:develop Mar 31, 2026
14 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants