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:
2026-02-13 12:12:36 -05:00
parent 81fbcfac11
commit b1fcf2ce95
3 changed files with 57 additions and 18 deletions
+44 -15
View File
@@ -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",