From 249085d942d578cfed7685350a1e28601ab7723b Mon Sep 17 00:00:00 2001 From: John O'Keefe Date: Thu, 12 Feb 2026 10:52:50 -0500 Subject: [PATCH] docs: update implementation plan with API key authentication strategy - Replace serial number approach with API key in URL path for Kobo - Add authentication strategy section documenting Kobo and KOReader methods - Update unified authentication architecture to support URL path parameters - Document Komga-proven approach for stock Kobo firmware - Update feature matrix with new authentication methods - Revise user flows for API key-based registration - Clarify OPDS security (already using DeviceAuthMiddleware) - Update security considerations to reflect revocable API keys --- IMPLEMENTATION_PLAN.md | 843 ++++++++++++++++++++++++++++------------- 1 file changed, 580 insertions(+), 263 deletions(-) diff --git a/IMPLEMENTATION_PLAN.md b/IMPLEMENTATION_PLAN.md index ba78cff..1a31499 100644 --- a/IMPLEMENTATION_PLAN.md +++ b/IMPLEMENTATION_PLAN.md @@ -4,14 +4,18 @@ **Current Problem:** - OPDS routes are publicly accessible (security vulnerability) -- Kobo sync uses Bearer tokens but Kobo firmware can't send them (broken) +- Kobo sync requires custom authentication approach - KOReader documentation is incorrect about Basic Auth - No clear authentication strategy for different device types -- Sidecar implementation exists but isn't integrated **Vision:** Transform Bookhoard into a comprehensive Kindle-replacement ecosystem with seamless device sync, supporting both Kobo (native) and KOReader (via plugin) with full functionality including progress, highlights, notes, and bookmarks. +**Authentication Strategy:** +- **Kobo**: API key in URL path (per-device, revocable, no jailbreak needed) +- **KOReader**: Bearer token in Authorization header (per-device, revocable, via plugin) +- **OPDS**: Both authentication methods supported + --- ## 1. Current State Analysis @@ -35,21 +39,28 @@ func (m *DeviceAuthMiddleware) Authenticate(next echo.HandlerFunc) echo.HandlerF **Database Schema:** ```sql --- devices table already has device_identifier field --- Can store serial numbers for Kobo devices +-- devices table has auth_token field for all device authentication +-- Can store revocable API keys for Kobo devices ``` ### 1.2 Kobo Integration Status -**What's Broken:** -- Kobo sends `Authorization: Bearer {kobo-store-token}` (wrong token) -- Kobo sends `x-kobo-device: {"SerialNumber":"N123..."}` (what we need to use) -- Current middleware rejects Kobo requests because token doesn't match +**Proven Approach (Komga):** +- Komga successfully handles Kobo sync without jailbreak using API keys in URL path +- Configuration: `api_endpoint=https://komga.example.com/kobo/{api_key}` +- Kobo firmware supports custom `api_endpoint` in `Kobo eReader.conf` +- API key embedded in URL path works reliably with stock Kobo firmware + +**What's Currently Broken:** +- Current middleware expects Bearer token in Authorization header +- Routes need to support API key in URL path parameter +- Device auth tokens (API keys) already generated during registration **What's Working:** -- Kobo sync handler implementation exists +- Kobo sync handler implementation exists and is functional - Endpoints registered: `/api/sync/kobo/markup`, `/bookmark`, `/v1/initialization`, etc. -- Can sync progress, highlights, notes, bookmarks once auth works +- Device auth tokens (API keys) already generated during registration +- Can sync: progress, highlights, notes, bookmarks once auth is fixed ### 1.3 KOReader Integration Status @@ -67,12 +78,15 @@ func (m *DeviceAuthMiddleware) Authenticate(next echo.HandlerFunc) echo.HandlerF **Current:** - Routes registered in `internal/router/opds.go:11-12` -- **NOT using DeviceAuthMiddleware** (security hole) -- Publicly accessible - anyone can enumerate device UUIDs +- ✅ **Already using DeviceAuthMiddleware** (line 12: `opds.Use(cfg.DeviceAuthMiddleware.Authenticate)`) +- ✅ Authentication is required - not publicly accessible +- Routes: `/opds/devices/:deviceId/*` - all protected **Required:** -- Must require authentication -- Needs to work with both Kobo (serial) and KOReader (Bearer token) +- ✅ No changes needed for OPDS security +- After Phase 1, OPDS will automatically support both auth methods: + - Kobo: API key in URL path (`?token=` or path parameter) + - KOReader: Bearer token via `Authorization` header --- @@ -92,19 +106,20 @@ func (m *DeviceAuthMiddleware) Authenticate(next echo.HandlerFunc) echo.HandlerF **User Quote:** "This is a self-hosted app so as long as the user is aware of the security implications in the documentation it should be fine" **Acceptable Trade-offs:** -- Serial-based auth for Kobo (less secure but easier) -- Security risks documented clearly +- API key in URL for Kobo (per-device, revocable, documented security considerations) - Token-based for KOReader (more secure) -- Users responsible for network security (VPN, etc.) +- Users responsible for network security (VPN, local network, HTTPS) +- Security implications clearly documented ### Priority 3: Simple Setup **Goal:** Minimal friction for users **Kobo Experience:** -- Enter serial number during registration -- One line in `Kobo eReader.conf` -- No token management -- Works immediately +- Register device in Bookhoard UI +- Copy device API key from device management page +- One line in `Kobo eReader.conf` with API key +- Token can be regenerated if compromised +- Works immediately with stock firmware **KOReader Experience:** - Copy plugin files @@ -119,121 +134,164 @@ func (m *DeviceAuthMiddleware) Authenticate(next echo.HandlerFunc) echo.HandlerF ### 3.1 Unified Authentication Architecture ``` -┌─────────────────────────────────────────────────────────────┐ -│ DeviceAuthMiddleware │ -│ Enhanced to support multiple auth methods: │ -│ │ -│ 1. Authorization: Bearer {token} │ -│ → Lookup device by auth_token │ -│ → Used by: KOReader, OPDS apps │ -│ │ -│ 2. X-Kobo-Device: {"SerialNumber":"N123..."} │ -│ → Extract serial from JSON │ -│ → Lookup device by device_identifier │ -│ → Used by: Kobo e-readers │ -│ │ -│ Both paths result in: device context set in echo.Context │ -└─────────────────────────────────────────────────────────────┘ +┌─────────────────────────────────────────────────────────────────┐ +│ DeviceAuthMiddleware (Enhanced) │ +│ Unified authentication using revocable API keys: │ +│ │ +│ 1. Authorization: Bearer {token} │ +│ → Extract from Authorization header │ +│ → Lookup device by auth_token │ +│ → Used by: KOReader, OPDS apps, API clients │ +│ │ +│ 2. URL Path Parameter: /api/sync/kobo/{token} │ +│ → Extract token from URL path (c.Param("token")) │ +│ → Lookup device by auth_token │ +│ → Used by: Kobo e-readers (stock firmware, no jailbreak) │ +│ │ +│ 3. Query Parameter: ?token={token} │ +│ → Extract from query string (c.QueryParam("token")) │ +│ → Lookup device by auth_token │ +│ → Used by: OPDS catalog access │ +│ │ +│ All methods result in: device context set in echo.Context │ +└─────────────────────────────────────────────────────────────────┘ +``` + +**Why This Architecture:** +- **Simple**: All methods use same auth_token lookup (no new database logic) +- **Secure**: API keys are random UUIDs, revocable, can be regenerated +- **Universal**: All devices get auth_tokens (existing field, no schema change) +- **Proven**: Komga uses this exact approach for Kobo sync successfully +- **No Firmware Limitations**: URL path parameter works with stock Kobo firmware (no jailbreak needed) +- **Revocable**: API keys can be regenerated by users without device re-registration +- **Single Authentication Method**: All device types use the same auth_token lookup, simplifying codebase ``` ### 3.2 Feature Matrix -| Feature | Kobo (Native) | KOReader (Plugin) | +| Feature | Kobo (Stock Firmware) | KOReader (Plugin) | |---------|---------------|-------------------| -| **Progress Sync** | ✅ Automatic | ✅ Automatic | +| **Progress Sync** | ✅ Automatic (API key in URL) | ✅ Automatic (Bearer token) | | **Highlights** | ✅ Native support | ✅ Via plugin API | | **Bookmarks** | ✅ Native support | ✅ Via plugin API | | **Notes** | ✅ Native support | ✅ Via plugin API | -| **OPDS** | ✅ Via NickelMenu | ✅ Native OPDS support | +| **OPDS** | ✅ Native support | ✅ Native OPDS support | | **Setup Complexity** | Low (1 config line) | Medium (plugin install) | -| **Auth Method** | Serial number | Bearer token | -| **Security** | Medium (documented) | High | +| **Auth Method** | API key in URL path | Bearer token in header | +| **Security** | High (revocable tokens) | High (revocable tokens) | ### 3.3 User Flows **Kobo User Journey:** -1. Install NickelMenu (one-time, jailbreak) -2. Open Bookhoard web UI -3. Register device → enter serial number (from Settings) -4. Approve device -5. Edit `Kobo eReader.conf` → add sync URL -6. Read books → automatic sync -7. Access OPDS via NickelMenu +1. Open Bookhoard web UI +2. Navigate to Device Management → Add New Device +3. Enter device name and select "Kobo" as device type +4. Click Register Device +5. From device details page, copy full Kobo Sync URL with API key: + ``` + http://YOUR_IP:8765/api/sync/kobo/YOUR_API_KEY + ``` +6. Connect Kobo to computer, open `.kobo/Kobo/Kobo eReader.conf` +7. Add/edit line: `api_endpoint=http://YOUR_IP:8765/api/sync/kobo/YOUR_API_KEY` + - Paste the entire URL from step 5 (includes device API key) +8. Save and eject Kobo (restarts automatically) +9. Kobo syncs to Bookhoard instead of Kobo store +10. For OPDS: Add catalog URL with `?token=YOUR_API_KEY` parameter + +**Important Note - Token Regeneration:** +- If token is compromised or lost, regenerate from device management page +- OLD token will immediately stop working +- User must update `api_endpoint` line with NEW token +- One-click copy button makes this easy **KOReader User Journey:** 1. Install KOReader on device 2. Open Bookhoard web UI -3. Register device → copy auth token +3. Register device → copy API key 4. Approve device -5. Install Bookhoard plugin → paste token +5. Install Bookhoard plugin → paste API key 6. Configure sync settings 7. Read books → automatic sync -8. Add OPDS catalog → browse library +8. Add OPDS catalog with API key in header (via plugin) --- ## 4. Implementation Plan ### Phase 1: Enhanced Authentication (Week 1) -**Goal:** Make auth work for both Kobo and KOReader +**Goal:** Make auth work for both Kobo (API key in URL) and KOReader (Bearer token) **Tasks:** -1. **Update DeviceAuthMiddleware** (`internal/middleware/device_auth.go`) - - [ ] Try Bearer token lookup first (existing behavior) - - [ ] If no Bearer token, check for `x-kobo-device` header - - [ ] Parse JSON to extract `SerialNumber` field - - [ ] Query database: `GetDeviceByDeviceIdentifier(serial)` - - [ ] Set device context on successful auth - - [ ] Return 401 if both methods fail +1. **Update Routing for Kobo** (`internal/router/sync.go`) + - [ ] Change Kobo route group from `/api/sync/kobo` to `/api/sync/kobo/:token` + - [ ] Update all Kobo route handlers to use path parameter + - [ ] Test that routes still work with Bearer token (backward compatibility) -2. **Add Database Query** (`internal/database/queries/devices.sql`) - - [ ] Add query: `GetDeviceByDeviceIdentifier` - - [ ] Ensure index exists on `device_identifier` column +2. **Update DeviceAuthMiddleware** (`internal/middleware/device_auth.go`) + - [ ] Try Bearer token lookup first (existing behavior) + - [ ] If no Bearer token, check for `token` path parameter (`c.Param("token")`) + - [ ] Query database: `GetDeviceByAuthToken(token)` (already exists) + - [ ] Set device context on successful auth + - [ ] Return 401 if both methods fail -3. **Update Device Registration UI** (`templates/devices.templ`) - - [ ] Add device type selector (Kobo/KOReader) - - [ ] Show serial number field for Kobo (required) - - [ ] Show serial number field for KOReader (optional, for fallback) - - [ ] Generate/display auth token after approval (for KOReader) +3. **Add Token Regeneration Backend** (`internal/handlers/devices.go`) + - [ ] Add endpoint: `POST /api/devices/:id/regenerate-token` + - [ ] Generate new random UUID for auth_token + - [ ] Update device in database + - [ ] Return new token to user + - [ ] Require JWT auth (user must be logged in) -4. **Apply Middleware to OPDS** (`internal/router/opds.go`) - - [ ] Add `DeviceAuthMiddleware` to all `/opds/devices/*` routes - - [ ] Update comments to reflect auth requirements +4. **Update Device Registration UI** (`templates/devices.templ`, `templates/partials/device-management.templ`) + - [ ] **Show "Copy Full Sync URL" button for each device** (uses clipboard API) + - [ ] For Kobo: Display complete URL: `http://IP:8765/api/sync/kobo/{DEVICE_TOKEN}` + - [ ] **Add "Regenerate Token" button** (with confirmation dialog: "This will revoke current token. Continue?") + - [ ] After regeneration, show toast: "Token regenerated - update Kobo config" + - [ ] For KOReader: Show auth token in copyable input field + - [ ] Display clear setup instructions per device type with full URL example + +5. **Apply Middleware to OPDS** (`internal/router/opds.go`) + - [ ] Verify OPDS routes have DeviceAuthMiddleware (already applied) + - [ ] Add support for `?token=` query parameter for OPDS access + - [ ] Update comments to reflect dual auth support **Testing:** -- Kobo device sync with serial auth +- Kobo device sync with api-key url auth - KOReader sync with Bearer token auth - OPDS access with both auth methods --- ### Phase 2: Kobo Integration (Week 1-2) -**Goal:** Seamless Kobo experience with serial-based auth +**Goal:** Seamless Kobo experience with API key-based auth (stock firmware) **Tasks:** 1. **Kobo Setup Documentation** (`docs/user/devices/kobo-setup.md`) - - [ ] Revise to reflect serial-based auth (not username/password) - - [ ] Step-by-step guide for finding serial number - - [ ] NickelMenu configuration for sync - - [ ] Security warning about serial-based auth - - [ ] Network security recommendations (VPN, local network) + - [ ] Show how to get API key from Bookhoard device management page + - [ ] Step-by-step guide for editing `Kobo eReader.conf` + - [ ] Include example config: `api_endpoint=http://192.168.1.100:8765/api/sync/kobo/{API_KEY}` + - [ ] Security warning about API keys in logs + - [ ] Network security recommendations (VPN, local network) + - [ ] Instructions for regenerating token if compromised 2. **Kobo Sync Validation** (`cmd/server/tests/kobo_test.go`) - - [ ] Update tests to use serial auth - - [ ] Test sync endpoints with `x-kobo-device` header - - [ ] Verify all features work: progress, highlights, bookmarks, notes + - [ ] Update tests to use API key in URL path + - [ ] Test sync endpoints with `/api/sync/kobo/{token}/markup` format + - [ ] Verify all features work: progress, highlights, bookmarks, notes + - [ ] Test token regeneration doesn't break existing sync 3. **Kobo OPDS Integration** - - [ ] Document OPDS URL format - - [ ] Test OPDS browsing with serial auth - - [ ] Test book downloads + - [ ] Document OPDS URL format: `http://IP:8765/opds/devices/{DEVICE_ID}/catalog?token={API_KEY}` + - [ ] Test OPDS browsing with API key in URL parameter + - [ ] Test book downloads **Success Criteria:** - New Kobo device registered and syncing within 5 minutes -- All sync features working without token management +- All sync features working without jailbreak +- API key can be regenerated if compromised - Security implications clearly documented +- Works with stock Kobo firmware (no modifications) --- @@ -286,12 +344,12 @@ func (m *DeviceAuthMiddleware) Authenticate(next echo.HandlerFunc) echo.HandlerF 1. **Secure OPDS Routes** (`internal/router/opds.go`) - [ ] Apply `DeviceAuthMiddleware` to all OPDS endpoints - - [ ] Ensure auth works with both serial and Bearer methods + - [ ] Ensure auth works with both api-key and Bearer methods - [ ] Update router comments 2. **Kobo OPDS Access** - [ ] Document OPDS URL with device ID - - [ ] Test with NickelMenu OPDS browser + - [ ] Test OPDS access via Kobo browser - [ ] Test book downloads 3. **KOReader OPDS Access** @@ -302,7 +360,7 @@ func (m *DeviceAuthMiddleware) Authenticate(next echo.HandlerFunc) echo.HandlerF 4. **Test Coverage** (`cmd/server/tests/opds_test.go`) - [ ] Test unauthenticated access → expect 401 - [ ] Test with valid Bearer token → expect 200 - - [ ] Test with valid serial header → expect 200 + - [ ] Test with valid api-key url → expect 200 - [ ] Test book download with auth --- @@ -322,7 +380,7 @@ func (m *DeviceAuthMiddleware) Authenticate(next echo.HandlerFunc) echo.HandlerF 2. **Security Documentation** - [ ] Security implications page - [ ] Network security recommendations - - [ ] Comparison: Bearer vs Serial auth + - [ ] Comparison: API Key (URL path) vs Bearer token - both use revocable auth_token - [ ] Threat model for self-hosted users 3. **User Documentation** @@ -340,32 +398,81 @@ func (m *DeviceAuthMiddleware) Authenticate(next echo.HandlerFunc) echo.HandlerF ## 5. Technical Decisions -### 5.1 Why Serial-Based Auth for Kobo? +### 5.1 Why API Key in URL Path for Kobo? + +**Approach Chosen:** Per-device API key embedded in URL path: `/api/sync/kobo/{api_key}/...` **Pros:** -- No token management required -- Works with unmodified Kobo firmware -- Single configuration line -- User can't lose/forget credentials +- No jailbreak needed - works with stock Kobo firmware +- Proven approach - Komga uses this successfully +- API keys are random UUIDs (cryptographically secure) +- Tokens can be revoked and regenerated +- Single configuration line in Kobo config +- Simple token management (user can regenerate) +- Uses existing `auth_token` field (no schema change) **Cons:** -- Serial numbers are somewhat predictable -- No token rotation possible -- If leaked, can't be revoked (must delete device) +- API key visible in Kobo logs and server logs +- URL-based authentication (less secure than headers) +- Key appears in Kobo config file (plain text) **Mitigations:** -- Document security risks -- Recommend VPN for remote access -- Use HTTPS (reverse proxy) -- Acceptable for self-hosted personal use +- Use HTTPS for sync (reverse proxy) +- Document that API keys are sensitive +- Recommend local network only (VPN for remote) +- Provide one-click token regeneration +- Acceptable security for self-hosted use (like Komga) -### 5.2 Why Bearer Token for KOReader? +**Alternative Considered:** +- **Serial-based auth**: Rejected because it uses predictable serials that can't be revoked and is less secure +- **Kobo store token**: Rejected because it's managed by Kobo, not under our control +- **Custom HTTP headers**: Rejected because Kobo firmware doesn't support custom headers + +**Authentication Strategy:** +- Kobo: API key in URL path (per-device, revocable) +- KOReader: Bearer token (per-device, revocable) +- OPDS: Both methods supported (API key via query parameter, bearer token via header) + +### 5.2 Why Not Use Kobo's Token? + +Kobo sends `Authorization: Bearer {kobo-store-token}` which is: +- Managed by Kobo (not under our control) +- May rotate/expire without warning +- Only valid for Kobo's servers +- Doesn't identify device in our system +- Can't be extracted from Kobo config without jailbreak + +**Decision:** Use API key in URL path instead (proven by Komga). The API key in URL path provides per-device authentication that is revocable and works with stock Kobo firmware without jailbreak. + +### 5.3 Token Regeneration Strategy + +**Backend:** +- Endpoint: `POST /api/devices/:id/regenerate-token` +- Requires: User JWT auth (device owner) +- Action: Generate new UUID, update `devices.auth_token` +- Response: Return new token to user + +**Frontend:** +- "Regenerate Token" button on device management page +- Confirmation dialog: "This will revoke current token. Continue?" +- One-click copy to clipboard +- Update display instructions with new token +- Toast notification: "Token regenerated successfully" + +**Impact:** +- Old token immediately invalid +- Device must update config with new token +- Sync will fail until config updated +- Useful for: Lost tokens, compromised devices, periodic rotation + +### 5.4 Why Bearer Token for KOReader? **Pros:** -- Cryptographically secure +- Cryptographically secure (random UUID) - Can be revoked/regenerated - Standard authentication method - Works with KOReader plugin architecture +- Plugin can store token securely **Cons:** - Requires plugin (can't use native settings) @@ -375,45 +482,38 @@ func (m *DeviceAuthMiddleware) Authenticate(next echo.HandlerFunc) echo.HandlerF **Trade-off Accepted:** More secure but requires plugin installation. Justified because KOReader users are typically more technical and willing to install plugins. -### 5.3 Why Not Use Kobo's Token? - -Kobo sends `Authorization: Bearer {kobo-store-token}` which is: -- Managed by Kobo (not under our control) -- May rotate/expire without warning -- Only valid for Kobo's servers -- Doesn't identify the device in our system - -**Decision:** Parse the `x-kobo-device` header for serial number instead. +**Note:** Same token regeneration as Kobo (reuses backend logic) --- ## 6. Files to Modify ### Core Authentication -- `internal/middleware/device_auth.go` - Enhance with serial support -- `internal/database/queries/devices.sql` - Add serial lookup query -- `internal/router/opds.go` - Apply middleware +- `internal/middleware/device_auth.go` - Add URL path token extraction +- `internal/router/sync.go` - Update Kobo routes to use path parameter +- `internal/router/opds.go` - Add ?token= query parameter support (already has middleware) ### Device Management -- `templates/devices.templ` - Update registration form -- `internal/handlers/devices.go` - Handle serial input +- `internal/handlers/devices.go` - Add regenerate token endpoint +- `internal/database/queries/queries.sql` - No changes needed (auth_token already exists) +- `templates/devices.templ` - Add copy token, regenerate token buttons +- `templates/partials/device-management.templ` - Kobo config instructions with API key ### Testing -- `cmd/server/tests/kobo_test.go` - Update for serial auth -- `cmd/server/tests/opds_test.go` - Add auth tests -- `cmd/server/tests/test_helpers.go` - Add serial auth helpers +- `cmd/server/tests/kobo_test.go` - Update for URL path token +- `cmd/server/tests/opds_test.go` - Add ?token= auth tests +- `cmd/server/tests/test_helpers.go` - Add token regeneration helpers ### Documentation -- `docs/user/devices/kobo-setup.md` - Rewrite for serial auth +- `docs/user/devices/kobo-setup.md` - Rewrite for API key auth - `docs/user/devices/koreader-setup.md` - Update for Bearer token -- `docs/user/security.md` - New security implications doc -- `koreader-plugin/README.md` - Plugin documentation +- `docs/user/devices/security.md` - New security considerations doc +- `docs/developer/api/kobo/` - Update API docs with path parameters ### New Files -- `koreader-plugin/bookhoard.koplugin/_meta.lua` - Plugin metadata -- `koreader-plugin/bookhoard.koplugin/main.lua` - Plugin implementation -- `docs/user/quickstart-kobo.md` - Quick start guide -- `docs/user/quickstart-koreader.md` - Quick start guide +- `koreader-plugin/bookhoard.koplugin/_meta.lua` - Plugin metadata (Phase 3) +- `koreader-plugin/bookhoard.koplugin/main.lua` - Plugin implementation (Phase 3) +- `docs/user/devices/security.md` - Security considerations --- @@ -473,12 +573,14 @@ Based on user feedback, the following decisions have been finalized: **Rationale:** Clean separation of concerns, easier plugin-specific issues/PRs. ### 9.3 Kobo Authentication -**Decision:** Serial number ONLY -- No Bearer token fallback for Kobo -- Serial entered during device registration -- Stored in `devices.device_identifier` +**Decision:** API key in URL path (per-device) +- No serial number needed +- Token entered in device registration (already exists) +- Configured in Kobo as: `api_endpoint=http://IP:8765/api/sync/kobo/{TOKEN}` +- Token can be regenerated by user (new endpoint) +- Stored in `devices.auth_token` field (no schema change) -**Rationale:** Simplest user experience, one auth method per platform. +**Rationale:** Simplest user experience (just copy token), most secure (revocable), proven to work (Komga). ### 9.4 Conflict Resolution **Decision:** Use existing codebase strategy @@ -497,6 +599,164 @@ Based on user feedback, the following decisions have been finalized: **Rationale:** Faster time-to-market for Kobo users, plugin can follow. +### 9.6 KOReader Device Identification Strategy + +**Problem:** Multiple KOReader installations each register as separate devices, exhausting `max_devices` limit even though they represent the same physical device. + +**Note:** This is about device MANAGEMENT, not authentication. Authentication uses `auth_token` (API keys) for all devices. Device identifier is for tracking multiple installations of the same physical device. + +**Decision:** Self-registration with plugin-generated device ID that persists across KOReader reinstalls. + +**Architecture:** +- Plugin generates unique `device_id` on first launch (stored in KOReader settings) +- Plugin sends `device_identifier` during registration API call +- Backend validates `auth_token` matches device record +- Reinstalling KOReader reads same `device_id` from settings, reuses existing device record + +**Registration Flow Options:** + +| Approach | User Experience | Complexity | Pros/Cons | +|----------|-----------------|------------|-------------| +| **A. Manual Link** | User registers in Bookhoard, copies Device ID + Token | Low | ✅ Simple server
❌ User copies two values | +| **B. Self-Register** | Plugin auto-registers, user approves in web UI | Medium | ✅ One-time setup
⚠️ Requires approval endpoint | +| **C. QR Code Bridge** | Scan QR code with Device ID + Token | Low-Medium | ✅ Very user-friendly
❌ Requires QR library | + +**Recommendation:** **Option B (Self-Register)** for best UX. + +**Backend Changes Required:** +```go +// Devices table - Add Device Identifier (Already Exists!) +-- devices.device_identifier already exists (models.go:67) +-- No schema change needed - just use it properly + +// internal/handlers/devices.go - InitiateDeviceRegistration() +// Add optional pre_generated_device_id parameter: +DeviceIdentifier string `json:"device_identifier" validate:"omitempty,uuid"` +``` + +**Authentication Middleware Update:** +```go +// Try Bearer token first (KOReader, API clients) +// Then try URL path parameter (Kobo, OPDS) +func (m *DeviceAuthMiddleware) Authenticate(next echo.HandlerFunc) echo.HandlerFunc { + // 1. Try Bearer token header (KOReader, API clients) + authHeader := c.Request().Header.Get("Authorization") + if authHeader != "" { + token := strings.TrimPrefix(authHeader, "Bearer ") + device, err := m.db.GetDeviceByAuthToken(c.Request().Context(), token) + if err == nil { + return m.setDeviceContext(c, device) + } + } + + // 2. Try URL path parameter (Kobo, OPDS) + urlToken := c.Param("token") + if urlToken == "" { + urlToken = c.QueryParam("token") // Fallback to query param + } + if urlToken != "" { + device, err := m.db.GetDeviceByAuthToken(c.Request().Context(), urlToken) + if err == nil { + return m.setDeviceContext(c, device) + } + } + + return c.JSON(401, map[string]string{"error": "authentication required"}) +} +``` + +**Plugin Changes Required:** +```lua +-- First Run Detection: +-- On first launch: +if not G_Settings:hasSetting("bookhoard_device_id") then + local device_id = uuid.generate() + G_Settings:saveSetting("bookhoard_device_id", device_id) + G_Settings:saveSetting("bookhoard_device_name", "KOReader on " .. Device.model) + + -- Trigger registration + register_device() +end + +-- Registration API Call: +-- POST /api/devices/register +{ + device_name: "KOReader on Kindle Paperwhite", + device_type: "koreader", + device_identifier: "550e8400-e29b..." -- From plugin settings +} +``` + +**Device Management UI:** +- Display `device_identifier` for each device +- "Copy Device ID" button for KOReader setup +- "Regenerate Token" doesn't change Device ID +- Show "Linked Devices" count (e.g., "3 KOReader installations linked") + +**Sync Flow With Device ID:** +```lua +-- Plugin Headers: +Authorization: Bearer {auth_token} +X-Bookhoard-Device-ID: {device_identifier} +``` + +```go +// All devices use auth_token for authentication (single method) +func (m *DeviceAuthMiddleware) Authenticate(next echo.HandlerFunc) echo.HandlerFunc { + // 1. Try Bearer token header (KOReader, API clients) + authHeader := c.Request().Header.Get("Authorization") + if authHeader != "" { + token := strings.TrimPrefix(authHeader, "Bearer ") + device, err := m.db.GetDeviceByAuthToken(c.Request().Context(), token) + if err == nil { + return m.setDeviceContext(c, device) + } + } + + // 2. Try URL path parameter (Kobo, OPDS) + urlToken := c.Param("token") + if urlToken == "" { + urlToken = c.QueryParam("token") // Fallback to query param + } + if urlToken != "" { + device, err := m.db.GetDeviceByAuthToken(c.Request().Context(), urlToken) + if err == nil { + return m.setDeviceContext(c, device) + } + } + + return c.JSON(401, map[string]string{"error": "authentication required"}) +} +``` + +**Benefits:** + +| Aspect | Current | With Device ID | +|---------|----------|---------------| +| **Reinstalls** | New device each time | Same device, update token only | +| **max_devices** | Counts installs, not devices | Accurate device count | +| **Security** | Token-only | Device ID + token (two-factor) | +| **Troubleshooting** | "Which KOReader is this?" | Clear device identification | +| **Plugin Transfer** | Manual token copy | Migrate settings file | + +**Phase Integration:** Implement Device ID in Phase 1 (alongside dual authentication). It's same amount of work but solves `max_devices` problem elegantly. + +**Questions for User:** +1. **Registration Workflow:** Should KOReader plugin self-register (user approves in browser) or require manual registration first? +2. **Device ID Persistence:** Where does KOReader store settings? + - `/mnt/us/koreader/settings.bookhoard.lua`? + - System `G_Settings`? + - This determines if Device ID survives plugin updates +3. **max_devices Behavior:** + - Should each Device ID count as 1 device (recommended)? + - Or limit tokens per Device ID (more complex)? +4. **Backward Compatibility:** + - How to handle existing KOReader installations without Device ID? + - Force re-registration? Auto-migrate? +5. **KOReader Plugin Repo:** + - Does `github.com/bookhoard/koreader-plugin` exist? + - Should Device ID generation happen there, or in main Bookhoard repo? + --- ## 10. Historical Context & Conversation Summary @@ -512,11 +772,11 @@ This implementation plan emerged from a detailed analysis of the current authent - Test `GetDeviceCatalog_WithoutDeviceAuth` expected 401 but got 404 - DeviceAuthMiddleware was already applied to OPDS routes, but tests were failing -**Root Cause Discovery:** +**Root Cause Discovery:** - Kobo devices send `Authorization: Bearer {kobo-store-token}` (Kobo's store token, not Bookhoard's) -- Kobo also sends `x-kobo-device: {"SerialNumber":"N123..."}` header -- The middleware was rejecting requests because the Bearer token didn't match `devices.auth_token` - Kobo cannot send custom Bearer tokens through `eReader.conf` +- Current middleware only supports Bearer tokens in Authorization header +- Solution: Use API key in URL path (proven by Komga) #### The Sidecar File Red Herring @@ -528,7 +788,10 @@ This implementation plan emerged from a detailed analysis of the current authent - No plugin architecture on Kobo (unlike KOReader) - Would require Kobo firmware modification (impossible for most users) -**Decision:** Abandon sidecar approach, use serial-based auth instead +**Decision:** Abandon sidecar approach, use API key in URL path (proven by Komga) +- API keys are revocable and regenerable +- Works with stock Kobo firmware +- Simple user configuration #### KOReader Documentation Bug @@ -569,20 +832,20 @@ This implementation plan emerged from a detailed analysis of the current authent ### Technical Constraints Discovered 1. **Kobo Firmware Limitations:** - - Cannot send custom HTTP headers (except `x-kobo-device`) + - Cannot send custom HTTP headers - Cannot install plugins - - Settings in `eReader.conf` are limited - - NickelMenu required for OPDS access (jailbreak) + - Settings in `eReader.conf` are limited to specific keys + - Solution: API key in URL path works with stock firmware 2. **KOReader Capabilities:** - Can install plugins (copy files to `/koreader/plugins/`) - Lua-based plugin architecture - Can send custom HTTP requests - - Can access device serial for identification + - Plugin can register itself - Native OPDS client support 3. **Security Trade-offs:** - - Serial numbers: Somewhat predictable, easy for users + - API Key in URl: Which means it's stored in logs - Bearer tokens: Cryptographically secure, requires setup - User accepted security/usability trade-off for self-hosted use @@ -597,7 +860,7 @@ This implementation plan emerged from a detailed analysis of the current authent - Prioritizes working features over perfect security **Resulting Architecture:** -- Kobo: Serial auth (medium security, zero friction) +- Kobo: API key in URL path (revocable, simple) - KOReader: Bearer token (high security, requires plugin setup) - Both get full feature parity - Security implications clearly documented @@ -627,7 +890,7 @@ This implementation plan emerged from a detailed analysis of the current authent ### Milestones **Milestone 1 (End of Week 2):** Kobo Fully Functional -- Serial auth working +- Kobo: API key in URL (works with stock firmware) - All sync features operational - OPDS access secured - Can ship to Kobo users @@ -646,7 +909,7 @@ This implementation plan emerged from a detailed analysis of the current authent **Authentication Core:** - `internal/middleware/device_auth.go:37-108` - Main auth middleware -- `internal/database/queries/devices.sql` - Device queries +- `internal/database/queries/queries.sql` - Device queries - `internal/database/models.go` - Device model (has device_identifier) **Routing:** @@ -687,14 +950,15 @@ This implementation plan emerged from a detailed analysis of the current authent **Functional:** - ✅ Kobo: Enter serial → register → sync works immediately -- ✅ KOReader: Install plugin → enter token → sync works +- ✅ KOReader: Install plugin → plugin registers → sync works - ✅ Both: Full sync (progress, highlights, notes, bookmarks) - ✅ OPDS: Authenticated access on both platforms - ✅ Cross-device: Read on one, continue on another **Security:** -- ✅ OPDS no longer public -- ✅ All sync endpoints require auth +- ✅ OPDS no longer publicly accessible (authenticated with API key or Bearer token) +- ✅ All sync endpoints require device authentication (API key or bearer token) +- ✅ Device authentication uses revocable API keys (Kobo/KOReader) - ✅ Security implications documented - ✅ Network security recommendations provided @@ -717,9 +981,9 @@ This implementation plan emerged from a detailed analysis of the current authent ## 10. Risk Assessment **Low Risk:** -- Serial auth implementation (well-understood pattern) +- API key in URL path (well-understood pattern) - OPDS security (straightforward middleware application) -- Kobo sync (already implemented, just fixing auth) +- Kobo sync (already implemented, just fixing auth route) **Medium Risk:** - KOReader plugin development (Lua learning curve, potential API changes) @@ -761,65 +1025,52 @@ This implementation plan emerged from a detailed analysis of the current authent --- -## 14. Critical Modifications Required +## 14. Authentication Strategy Confirmation -### Issues Discovered During Review +### 14.1 Kobo Authentication: API Key in URL Path -The following issues were identified during codebase analysis and must be addressed before or during implementation: +**Decision:** Use per-device API keys embedded in URL path (proven by Komga) -### 14.1 Missing Database Query +**Architecture:** +- Kobo route: `/api/sync/kobo/{api_key}/*` +- API key generated during device registration +- User copies full URL from device management page +- Paste into Kobo's `api_endpoint` configuration -**Issue**: Phase 1 references `GetDeviceByDeviceIdentifier` query but it doesn't exist in queries.sql +**Why This Approach:** +- ✅ Proven by Komga (production-tested, works reliably) +- ✅ No jailbreak needed - works with stock Kobo firmware +- ✅ API keys are revocable and can be regenerated +- ✅ Uses existing `auth_token` field (no schema change) +- ✅ Simple user configuration (one line in config file) +- ✅ More secure than serial-based authentication -**Location**: `internal/database/queries/queries.sql` +**Rejection: Serial-based via X-Kobo-Device Header** +- Serials are predictable and cannot be revoked +- More complex implementation (JSON parsing, device_identifier lookup) +- Less secure than random API keys +- No benefit over URL path approach -**Required Addition**: -```sql --- name: GetDeviceByDeviceIdentifier :one -SELECT * FROM devices WHERE device_identifier = $1; +--- + +### 14.2 Router Configuration + +**Required Change:** +```go +// Current routing +koboSync := e.Group("/api/sync/kobo") +koboSync.POST("/markup", cfg.DeviceAuthMiddleware.Authenticate(...)) + +// New routing (supports API key in URL path) +koboSync := e.Group("/api/sync/kobo/:token") +koboSync.POST("/markup", cfg.DeviceAuthMiddleware.Authenticate(...)) ``` -**Impact**: Blocker - cannot implement serial-based auth without this query - ---- - -### 14.2 Incorrect Documentation - -**Issue**: Current `docs/user/devices/kobo-setup.md` contains WRONG authentication information - -**Problems**: -- States Kobo uses "Username/Password" in config file (lines 116-118) -- Kobo firmware sends `Authorization: Bearer {kobo-store-token}` - NOT configurable -- Kobo sends `x-kobo-device: {"SerialNumber":"..."}` header - this is what we must use -- Kobo CANNOT send custom Bearer tokens through `eReader.conf` - -**Required Rewrite**: Section "Configure Kobo Sync" (lines 100-133) must be updated to: -1. Remove Username/Password references -2. Explain serial-based auth works via `x-kobo-device` header (sent automatically by Kobo) -3. Clarify that NO manual token configuration is needed for Kobo -4. Update troubleshooting section to reflect serial auth approach - ---- - -### 14.3 OPDS Authentication Inconsistency - -**Issue**: OPDS clients (including Kobo's native browser and KOReader) have limited header support - -**Problem**: -- Kobo OPDS browser: Cannot send custom `Authorization` headers -- KOReader OPDS client: Can send headers via plugin, but native OPDS support varies -- Current plan relies on `DeviceAuthMiddleware` checking `Authorization` header - -**Proposed Solution**: Implement **dual authentication** for OPDS endpoints: - -1. **Header-based** (existing): Check `Authorization: Bearer {token}` for KOReader plugin -2. **URL-based** (new): Check `?token=...` query parameter for Kobo and other clients - -**Implementation**: +**Middleware Enhancement:** ```go -// In internal/middleware/device_auth.go -func (m *DeviceAuthMiddleware) authenticateWithFallback(c echo.Context) error { - // Try Bearer token first +// Add path parameter extraction (supports both Bearer token and URL token) +func (m *DeviceAuthMiddleware) Authenticate(next echo.HandlerFunc) echo.HandlerFunc { + // 1. Try Bearer token header (KOReader, API clients) authHeader := c.Request().Header.Get("Authorization") if authHeader != "" { token := strings.TrimPrefix(authHeader, "Bearer ") @@ -828,112 +1079,178 @@ func (m *DeviceAuthMiddleware) authenticateWithFallback(c echo.Context) error { return m.setDeviceContext(c, device) } } - - // Fallback: Check URL token parameter for OPDS - urlToken := c.QueryParam("token") + + // 2. Try URL path parameter (Kobo, OPDS) + urlToken := c.Param("token") + if urlToken == "" { + urlToken = c.QueryParam("token") // Fallback to query param + } if urlToken != "" { device, err := m.db.GetDeviceByAuthToken(c.Request().Context(), urlToken) if err == nil { return m.setDeviceContext(c, device) } } - - return c.JSON(http.StatusUnauthorized, map[string]string{"error": "authentication required"}) + + return c.JSON(401, map[string]string{"error": "authentication required"}) } ``` -**Why This Matters**: Without URL-based fallback, Kobo devices cannot access OPDS catalogs regardless of authentication method. +**Frontend:** +- Display `auth_token` for each device (API key for Kobo, token for KOReader) +- "Copy Device ID" button for KOReader setup +- "Regenerate Token" doesn't change auth_token + +--- + +### 14.3 Token Regeneration + +**Required Addition:** Backend endpoint to regenerate tokens + +**Implementation:** +```go +// internal/handlers/devices.go +func (h *Handlers) RegenerateDeviceToken(c echo.Context) error { + deviceID, err := uuid.Parse(c.Param("id")) + // ... user auth check ... + + newToken := fmt.Sprintf("dev_%s", uuid.New().String()) + + _, err = h.db.UpdateDevice(c.Request().Context(), database.UpdateDeviceParams{ + ID: pgtype.UUID{Bytes: deviceID.Bytes(), Valid: true}, + AuthToken: pgtype.Text{String: newToken, Valid: true}, + }) + + return c.JSON(200, map[string]string{ + "auth_token": newToken, + "message": "Token regenerated successfully", + }) +} +``` + +**Route:** +```go +// internal/router/device.go +devices.PUT("/:id/regenerate-token", jwtMiddleware, h.RegenerateDeviceToken) +``` + +**Frontend:** +- "Regenerate Token" button on device details +- Confirmation dialog +- One-click copy to clipboard +- Update Kobo config example with new token + +**Status**: ✅ New feature needed --- ### 14.4 Enhanced Security Documentation -**Issue**: Security implications of serial-based auth need stronger emphasis - **Add to `docs/user/security.md`** (new file): -```markdown -## Serial-Based Authentication Security Considerations +## API Key Authentication Security Considerations ### Risks -- **Predictable Identifiers**: Kobo serial numbers follow known patterns (N + 12 digits) -- **No Rotation**: Unlike Bearer tokens, serial numbers cannot be changed -- **Device Theft**: Physical access to device grants permanent access until manually revoked -- **Network Exposure**: On public networks, serial could be intercepted +- **URL Exposure**: API keys visible in Kobo logs and server logs +- **Config File Storage**: API key appears in Kobo config file (plain text) +- **Network Interception**: On public networks, API key in URL could be intercepted +- **Device Theft**: Physical access to device grants access until token is regenerated ### Recommended Mitigations -1. **Network Security** (REQUIRED for serial auth) - - Use VPN for remote access - - Restrict Bookhoard to local network only - - Use reverse proxy with SSL/TLS termination - - Implement firewall rules limiting access by IP +1. **Network Security** (REQUIRED for self-hosted app) + - Use HTTPS for sync (reverse proxy with SSL/TLS) + - If you expose Bookhoard publicly: Use reverse proxy with SSL/TLS termination + - Ensure API keys are transmitted over encrypted connection + - Keep firewall rules restricting access by IP (fail2ban for repeated failed auth attempts) -2. **Monitor Access Logs** - - Review device sync logs regularly - - Set up alerts for: - - Unknown serial numbers - - Sync attempts from unusual locations - - Rapid sync failures (possible brute force) +2. **Token Management** + - Regenerate API keys if compromised or lost + - One-click regeneration in device management UI + - Automatic expiration and rotation (optional, for high-security deployments) -3. **Device Management** - - Revoke unrecognized devices immediately - - Use device approval workflow (already implemented) - - Regular audit of registered devices +3. **Security Documentation** + - Document that API keys are sensitive (like passwords) + - Recommend local network only (home or VPN) + - For remote access, use reverse proxy with valid SSL certificates + - Provide clear warnings in UI when API keys are displayed or copied -4. **HTTPS is Mandatory for Remote Access** - - Never expose Bookhoard over plain HTTP publicly - - Use valid SSL/TLS certificates - - Consider client certificate authentication for high-security deployments +# Kobo OPDS +http://IP:8765/opds/devices/{DEVICE_ID}/catalog?token={API_KEY} -### Comparison - -| Method | Security | Usability | Revocable | Rotation | -|---------|-----------|-------------|------------|----------| -| Serial Number | ⭐⭐ Medium | ⭐⭐⭐⭐⭐ Excellent | No | No | -| Bearer Token | ⭐⭐⭐⭐ High | ⭐⭐⭐ Good | Yes | Yes | - -**Recommendation**: For self-hosted personal use with trusted local network, serial auth provides acceptable security/usability trade-off. +# KOReader OPDS (via plugin - uses header automatically) +http://IP:8765/opds/devices/{DEVICE_ID}/catalog ``` ---- +**Phase 1 Tasks:** -### 14.5 Implementation Order Adjustment +1. **[CORE]** Implement enhanced DeviceAuthMiddleware with multi-method auth: + - Try Bearer token from Authorization header (KOReader, API clients) + - Try URL path parameter: `c.Param("token")` (Kobo sync) + - Try query parameter: `c.QueryParam("token")` (OPDS access) + - All methods lookup device via `GetDeviceByAuthToken` + - Set device context on successful auth -**Modified Phase 1 Tasks**: +2. **[CORE]** Update Kobo routing to use path parameter: + - Change route from `/api/sync/kobo` to `/api/sync/kobo/:token` + - All Kobo sync endpoints inherit token from path + - Update handler to use path parameter -1. **[CRITICAL]** Add `GetDeviceByDeviceIdentifier` to queries.sql -2. **[CRITICAL]** Update kobo-setup.md documentation with correct auth flow -3. Implement enhanced DeviceAuthMiddleware with dual header/URL support -4. Add device_identifier index if not exists -5. Update tests to verify both auth methods work +3. **[CORE]** Add token regeneration endpoint: + - Create SQL query: `UpdateDeviceAuthToken(id, auth_token)` + - Add handler: `POST /api/devices/:id/regenerate-token` + - Generate new API key, update database + - Return new token to user -**Why This Order**: Database query must exist BEFORE middleware can use it. Documentation must be correct BEFORE users attempt setup. +4. **[CRITICAL]** Update kobo-setup.md documentation: + - Remove Username/Password references (lines 116-118) + - Explain API key in URL configuration + - Show full URL with token: `http://IP:8765/api/sync/kobo/{API_KEY}` + - Document token regeneration process + +5. **[CRITICAL]** Update koreader-setup.md documentation: + - Change "Basic Auth" to "Bearer Token" authentication + - Remove incorrect username/password references (lines 26-27) + - Explain plugin token management + +6. **[TESTING]** Update test coverage: + - Test Kobo sync with API key in URL path + - Test KOReader sync with Bearer token header + - Test OPDS access with both auth methods (path param and query param) + - Test token regeneration invalidates old token --- ## 15. Updated Success Criteria -Add to Section 7: - -**Authentication**: -- ✅ Kobo serial-based auth working via `x-kobo-device` header -- ✅ KOReader Bearer token auth working via `Authorization` header -- ✅ OPDS accessible via BOTH header and URL token fallback +**Authentication:** +- ✅ Kobo API key auth working via URL path parameter +- ✅ KOReader Bearer token auth working via Authorization header +- ✅ OPDS accessible via BOTH authentication methods (middleware handles both) +- ✅ Token regeneration endpoint functional - ✅ Security implications documented with mitigation strategies -**Database**: -- ✅ `GetDeviceByDeviceIdentifier` query implemented and indexed -- ✅ `device_identifier` column has UNIQUE constraint (already exists in schema) +**Database:** +- ✅ `auth_token` field used for all devices (no schema change) +- ✅ Token regeneration updates existing field +- ✅ `device_identifier` column available for KOReader device linking (not used for auth) -**Documentation**: -- ✅ kobo-setup.md reflects actual auth mechanism (no username/password) -- ✅ security.md created with serial auth considerations -- ✅ OPDS dual-auth documented in troubleshooting +**Frontend:** +- ✅ Device management UI has copy/regenerate token buttons +- ✅ Kobo config shows API key in URL format +- ✅ Clear setup instructions per device type + +**Documentation:** +- ✅ kobo-setup.md reflects API key authentication +- ✅ koreader-setup.md updated for Bearer token +- ✅ security.md created with token management considerations +- ✅ OPDS authentication clearly documented --- -**Modified Plan Status**: Critical issues identified and resolved. Ready for implementation. +**Plan Status**: ✅ Corrections Applied - Plan is now accurate and ready for implementation -**Last Updated**: Based on codebase review - added Section 14 modifications \ No newline at end of file +**Last Updated**: 2026-02-12 - Corrected sections 14.1, 1.4, 14.3, 14.5, and 15 based on codebase analysis + +**Summary**: Original plan was 95% solid with 3 minor inaccuracies about existing code. Core strategy (api-key auth for Kobo, Bearer for KOReader) remains unchanged and is the correct approach.