feat: support multiple device authentication methods
- Bearer token in Authorization header (KOReader, API clients) - URL path parameter (Kobo sync: /api/sync/kobo/:token/...) - Query parameter (OPDS: ?token=...) - Update Kobo sync routes to use token in path - Add authentication method documentation to OPDS routes
This commit is contained in:
@@ -36,28 +36,57 @@ func NewDeviceAuthMiddleware(db *database.Queries) *DeviceAuthMiddleware {
|
||||
|
||||
func (m *DeviceAuthMiddleware) Authenticate(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
var device database.Devices
|
||||
var err error
|
||||
var token string
|
||||
var urlToken string
|
||||
var queryToken string
|
||||
|
||||
// Method 1: Try Bearer token header (KOReader, API clients, OPDS)
|
||||
authHeader := c.Request().Header.Get("Authorization")
|
||||
if authHeader == "" {
|
||||
return c.JSON(http.StatusUnauthorized, map[string]string{
|
||||
"error": "missing authorization header",
|
||||
})
|
||||
if authHeader != "" {
|
||||
if !strings.HasPrefix(authHeader, "Bearer ") {
|
||||
return c.JSON(http.StatusUnauthorized, map[string]string{
|
||||
"error": "invalid authorization header format",
|
||||
})
|
||||
}
|
||||
|
||||
token = strings.TrimPrefix(authHeader, "Bearer ")
|
||||
device, err = m.db.GetDeviceByAuthToken(c.Request().Context(), token)
|
||||
if err == nil {
|
||||
// Found device via Bearer token, continue to validation
|
||||
goto validateDevice
|
||||
}
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(authHeader, "Bearer ") {
|
||||
return c.JSON(http.StatusUnauthorized, map[string]string{
|
||||
"error": "invalid authorization header format",
|
||||
})
|
||||
// Method 2: Try URL path parameter (Kobo sync, OPDS)
|
||||
// Route format: /api/sync/kobo/:token/...
|
||||
urlToken = c.Param("token")
|
||||
if urlToken != "" {
|
||||
device, err = m.db.GetDeviceByAuthToken(c.Request().Context(), urlToken)
|
||||
if err == nil {
|
||||
// Found device via URL path token, continue to validation
|
||||
goto validateDevice
|
||||
}
|
||||
}
|
||||
|
||||
token := strings.TrimPrefix(authHeader, "Bearer ")
|
||||
|
||||
device, err := m.db.GetDeviceByAuthToken(c.Request().Context(), token)
|
||||
if err != nil {
|
||||
return c.JSON(http.StatusUnauthorized, map[string]string{
|
||||
"error": "invalid device token",
|
||||
})
|
||||
// Method 3: Try query parameter (OPDS catalog access)
|
||||
// URL format: /opds/devices/:deviceId/catalog?token=...
|
||||
queryToken = c.QueryParam("token")
|
||||
if queryToken != "" {
|
||||
device, err = m.db.GetDeviceByAuthToken(c.Request().Context(), queryToken)
|
||||
if err == nil {
|
||||
// Found device via query parameter token, continue to validation
|
||||
goto validateDevice
|
||||
}
|
||||
}
|
||||
|
||||
// All authentication methods failed
|
||||
return c.JSON(http.StatusUnauthorized, map[string]string{
|
||||
"error": "authentication required - use Bearer token or API key",
|
||||
})
|
||||
|
||||
validateDevice:
|
||||
if !device.SyncEnabled.Bool || !device.SyncEnabled.Valid {
|
||||
return c.JSON(http.StatusForbidden, map[string]string{
|
||||
"error": "device sync is disabled",
|
||||
|
||||
+10
-2
@@ -1,9 +1,17 @@
|
||||
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
|
||||
//
|
||||
// Authentication Methods:
|
||||
// - Kobo devices: URL path parameter (e.g., /opds/devices/kobo-clara/catalog?token=dev_abc...)
|
||||
// (Token stored in device for use in stock firmware sync)
|
||||
//
|
||||
// - KOReader devices: Bearer token in Authorization header (e.g., Authorization: Bearer dev_xyz...)
|
||||
// (Token configured in device settings, passed to plugins)
|
||||
//
|
||||
// Middleware supports both methods (see device_auth.go)
|
||||
// Returns 401 Unauthorized if device token is missing, invalid, or device sync is disabled
|
||||
|
||||
func registerOPDSRoutes(cfg *Config) {
|
||||
e := cfg.Echo
|
||||
|
||||
|
||||
@@ -29,8 +29,10 @@ func registerSyncRoutes(cfg *Config) {
|
||||
koreaderSync.POST("/bookmarks", cfg.DeviceAuthMiddleware.Authenticate(cfg.KOReaderHandler.SyncBookmarks))
|
||||
|
||||
// Kobo sync routes (device authentication required)
|
||||
// Kobo devices use URL path: /api/sync/kobo/{token}/markup
|
||||
// API clients can use Authorization header: Authorization: Bearer {token}
|
||||
koboHandler := handlers.NewKoboHandler(cfg.Queries, cfg.ConnManager)
|
||||
koboSync := e.Group("/api/sync/kobo")
|
||||
koboSync := e.Group("/api/sync/kobo/:token")
|
||||
koboSync.POST("/markup", cfg.DeviceAuthMiddleware.Authenticate(koboHandler.Markup))
|
||||
koboSync.POST("/bookmark", cfg.DeviceAuthMiddleware.Authenticate(koboHandler.Bookmark))
|
||||
koboSync.POST("/v1/analytics/gettests", cfg.DeviceAuthMiddleware.Authenticate(koboHandler.AnalyticsGettests))
|
||||
|
||||
Reference in New Issue
Block a user