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 - Update test expectations to require 401 for unauthenticated requests - Fix query parameter name from 'query' to 'q' in search endpoints - Update router comments to clarify authentication requirements
This commit is contained in:
@@ -23,9 +23,8 @@ func TestOPDSEndpoints(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
// OPDS endpoints may or may not require auth - depends on implementation
|
// OPDS endpoints require device authentication via devices.auth_token
|
||||||
// Accept either 200 (public) or 401 (requires auth)
|
assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
|
||||||
assert.True(t, resp.StatusCode == http.StatusOK || resp.StatusCode == http.StatusUnauthorized)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("GetDeviceCatalog_InvalidDeviceID", func(t *testing.T) {
|
t.Run("GetDeviceCatalog_InvalidDeviceID", func(t *testing.T) {
|
||||||
@@ -54,7 +53,7 @@ func TestOPDSEndpoints(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
t.Run("SearchDeviceCatalog_InvalidDeviceID", func(t *testing.T) {
|
t.Run("SearchDeviceCatalog_InvalidDeviceID", func(t *testing.T) {
|
||||||
httpReq, _ := http.NewRequest("GET", setup.Server.URL+"/opds/devices/invalid-uuid/search?query=test", nil)
|
httpReq, _ := http.NewRequest("GET", setup.Server.URL+"/opds/devices/invalid-uuid/search?q=test", nil)
|
||||||
|
|
||||||
resp, err := client.Do(httpReq)
|
resp, err := client.Do(httpReq)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@@ -66,7 +65,7 @@ func TestOPDSEndpoints(t *testing.T) {
|
|||||||
t.Run("SearchDeviceCatalog_ValidDevice", func(t *testing.T) {
|
t.Run("SearchDeviceCatalog_ValidDevice", func(t *testing.T) {
|
||||||
deviceID := uuid.New()
|
deviceID := uuid.New()
|
||||||
|
|
||||||
httpReq, _ := http.NewRequest("GET", setup.Server.URL+"/opds/devices/"+deviceID.String()+"/search?query=test", nil)
|
httpReq, _ := http.NewRequest("GET", setup.Server.URL+"/opds/devices/"+deviceID.String()+"/search?q=test", nil)
|
||||||
httpReq.Header.Set("Authorization", "Bearer "+token)
|
httpReq.Header.Set("Authorization", "Bearer "+token)
|
||||||
|
|
||||||
resp, err := client.Do(httpReq)
|
resp, err := client.Do(httpReq)
|
||||||
@@ -280,7 +279,7 @@ func TestOPDSEdgeCases(t *testing.T) {
|
|||||||
deviceID := uuid.New()
|
deviceID := uuid.New()
|
||||||
|
|
||||||
// Search with special characters
|
// Search with special characters
|
||||||
httpReq, _ := http.NewRequest("GET", setup.Server.URL+"/opds/devices/"+deviceID.String()+"/search?query=test%20%26%20more", nil)
|
httpReq, _ := http.NewRequest("GET", setup.Server.URL+"/opds/devices/"+deviceID.String()+"/search?q=test%20%26%20more", nil)
|
||||||
httpReq.Header.Set("Authorization", "Bearer "+token)
|
httpReq.Header.Set("Authorization", "Bearer "+token)
|
||||||
|
|
||||||
resp, err := client.Do(httpReq)
|
resp, err := client.Do(httpReq)
|
||||||
@@ -294,7 +293,7 @@ func TestOPDSEdgeCases(t *testing.T) {
|
|||||||
t.Run("Search_EmptyQuery", func(t *testing.T) {
|
t.Run("Search_EmptyQuery", func(t *testing.T) {
|
||||||
deviceID := uuid.New()
|
deviceID := uuid.New()
|
||||||
|
|
||||||
httpReq, _ := http.NewRequest("GET", setup.Server.URL+"/opds/devices/"+deviceID.String()+"/search?query=", nil)
|
httpReq, _ := http.NewRequest("GET", setup.Server.URL+"/opds/devices/"+deviceID.String()+"/search?q=", nil)
|
||||||
httpReq.Header.Set("Authorization", "Bearer "+token)
|
httpReq.Header.Set("Authorization", "Bearer "+token)
|
||||||
|
|
||||||
resp, err := client.Do(httpReq)
|
resp, err := client.Do(httpReq)
|
||||||
|
|||||||
@@ -1,11 +1,15 @@
|
|||||||
package router
|
package router
|
||||||
|
|
||||||
|
// 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) {
|
func registerOPDSRoutes(cfg *Config) {
|
||||||
e := cfg.Echo
|
e := cfg.Echo
|
||||||
|
|
||||||
// OPDS routes (public - device authentication optional)
|
// Require device authentication for all OPDS endpoints
|
||||||
// Note: OPDSHandler implements its own device authentication
|
|
||||||
opds := e.Group("/opds/devices")
|
opds := e.Group("/opds/devices")
|
||||||
|
opds.Use(cfg.DeviceAuthMiddleware.Authenticate)
|
||||||
opds.GET("/:deviceId/catalog", cfg.OPDSHandler.GetDeviceCatalog)
|
opds.GET("/:deviceId/catalog", cfg.OPDSHandler.GetDeviceCatalog)
|
||||||
opds.GET("/:deviceId/search", cfg.OPDSHandler.SearchDeviceCatalog)
|
opds.GET("/:deviceId/search", cfg.OPDSHandler.SearchDeviceCatalog)
|
||||||
opds.GET("/:deviceId/nav", cfg.OPDSHandler.GetDeviceNavigation)
|
opds.GET("/:deviceId/nav", cfg.OPDSHandler.GetDeviceNavigation)
|
||||||
|
|||||||
Reference in New Issue
Block a user