5.6 KiB
5.6 KiB
Fix OPDS Device Authentication
Problem Summary
Current State:
- OPDS routes (
/opds/devices/*) are public - no authentication - Device sync API (
/api/sync/kobo/*,/api/sync/koreader/*) usesDeviceAuthMiddleware→ validatesdevices.auth_token - Anyone can enumerate/access device UUIDs without authentication
- Test
GetDeviceCatalog_WithoutDeviceAuthexpects 401 (auth required) but gets 404 (device not found)
Design Principle: Devices belong to users; OPDS access should require device authentication via devices.auth_token (same model as sync API).
Step-by-Step Implementation Plan
Step 1: Apply DeviceAuthMiddleware to OPDS Routes
File: internal/router/opds.go
Changes:
func registerOPDSRoutes(cfg *Config) {
e := cfg.Echo
// Apply device authentication middleware
opds := e.Group("/opds/devices")
opds.Use(cfg.DeviceAuthMiddleware.Authenticate) // ← ADD THIS
// OPDS routes now require valid devices.auth_token
opds.GET("/:deviceId/catalog", cfg.OPDSHandler.GetDeviceCatalog)
opds.GET("/:deviceId/search", cfg.OPDSHandler.SearchDeviceCatalog)
opds.GET("/:deviceId/nav", cfg.OPDSHandler.GetDeviceNavigation)
opds.GET("/:deviceId/download/:bookId", cfg.OPDSHandler.DownloadBook)
opds.GET("/:deviceId/cover/:bookId", cfg.OPDSHandler.GetCoverImage)
opds.GET("/:deviceId/formats/:bookId", cfg.OPDSHandler.ListFormats)
}
Rationale:
- Reuses existing authentication pattern (proven on sync endpoints)
- Middleware validates
devices.auth_tokenfromAuthorization: Bearer {token}header - Returns 401 if missing/invalid/expired token
- Device must belong to user (already enforced in device table)
Step 2: Update OPDS Handler Comments
File: internal/router/opds.go
Change:
// Register OPDS routes with device authentication
// Devices must use their devices.auth_token (generated during device registration/approval)
// Kobo devices store this token for both sync and OPDS catalog access
// Returns 401 Unauthorized if device token is missing, invalid, or device sync is disabled
func registerOPDSRoutes(cfg *Config) {
e := cfg.Echo
// Require device authentication for all OPDS endpoints
opds := e.Group("/opds/devices")
opds.Use(cfg.DeviceAuthMiddleware.Authenticate)
...
}
Remove misleading comment:
- // OPDS routes (public - device authentication optional)
- // Note: OPDSHandler implements its own device authentication
Step 3: Update Test Expectations
File: cmd/server/tests/opds_test.go
Change 1 - Remove invalid 200 option:
// Line 18-29: GetDeviceCatalog_WithoutDeviceAuth
t.Run("GetDeviceCatalog_WithoutDeviceAuth", func(t *testing.T) {
deviceID := uuid.New()
httpReq, _ := http.NewRequest("GET", setup.Server.URL+"/opds/devices/"+deviceID.String()+"/catalog", nil)
resp, err := client.Do(httpReq)
require.NoError(t, err)
defer resp.Body.Close()
// OPDS endpoints require device authentication
assert.Equal(t, http.StatusUnauthorized, resp.StatusCode) // ← Expect 401 only
})
Change 2 - Update comment:
// Test: OPDS endpoints require device authentication via devices.auth_token
Step 4: Update Additional OPDS Edge Case Test
File: cmd/server/tests/opds_test.go
Change: Update TestOPDSEdgeCases/Search_SpecialCharacters to use auth token:
t.Run("Search_SpecialCharacters", func(t *testing.T) {
deviceID := uuid.New()
// Search with special characters (requires valid device auth token)
httpReq, _ := http.NewRequest("GET", setup.Server.URL+"/opds/devices/"+deviceID.String()+"/search?q=test%20%26%20more", nil)
httpReq.Header.Set("Authorization", "Bearer "+setup.DeviceToken) // ← ADD THIS
Step 5: Update Router Comment
File: internal/router/opds.go
Add documentation:
// Register OPDS routes with device authentication
// Devices must use their devices.auth_token (generated during device registration/approval)
// Kobo devices store this token for both sync and OPDS catalog access
// Returns 401 Unauthorized if device token is missing, invalid, or device sync is disabled
func registerOPDSRoutes(cfg *Config) {
e := cfg.Echo
// Require device authentication for all OPDS endpoints
opds := e.Group("/opds/devices")
opds.Use(cfg.DeviceAuthMiddleware.Authenticate)
...
}
Test Verification Plan
After implementing changes, verify:
GetDeviceCatalog_WithoutDeviceAuth- Should return 401 (no auth token provided)GetDeviceCatalog_ValidDevice- Withsetup.DeviceTokenshould return 200 (valid device)SearchDeviceCatalog_ValidDevice- With device token should return 200 or 404 (no results)TestOPDSEdgeCases- All should usesetup.DeviceTokenfor authentication
Git Commit Structure
Commit 1: Apply DeviceAuthMiddleware to OPDS routes
fix(opds): Require device authentication for OPDS catalog endpoints
- Apply DeviceAuthMiddleware.Authenticate to /opds/devices/* routes
- OPDS now uses same authentication model as sync API (devices.auth_token)
- Removes security vulnerability allowing unauthorized device enumeration
- Updates router comments to clarify authentication requirements
Refs: #PROJECT_GUIDELINES.md
Commit 2: Update OPDS test expectations
test(opds): Update test expectations for device authentication
- GetDeviceCatalog_WithoutDeviceAuth: Expect 401 (auth required)
- TestOPDSEdgeCases: Use setup.DeviceToken for authenticated requests
- Remove misleading "public or 401" test expectations
Refs: #PROJECT_GUIDELINES.md