Delete the kagane-specific cover path (#63)
The web proxy for kagane covers is dead: since #62 every Site's cover bytes land in the content-addressed store at creation or on the poll, and the one public route serves them all. Remove the second way to reach a Cover: - Bookmark.CoverURL() and the templates' use of it: templates render the wire value (.Cover) and nothing else. - GET /img/kagane/{id}, web.CoverFetcher, coverIDRe: the route and its identifier validation are gone, and with them web/cover.go. - store.KaganeImageID, GetKaganeCover, PutKaganeCover, kaganeCoverSourceURL: the proxy's persistence. - Bookmark.CoverSource: dead once CoverURL is gone. Acquisition keeps the browser where kagane genuinely needs it, but the Site name leaves the routing: kaganeImageURLRe lives in browser.go with the rest of the per-Site knowledge, browserCoverURL claims the URLs the sidecar alone can fetch, and fetchCoverBytes routes by URL shape with no Site argument. No plain-TLS fallback for a claimed URL — that would only retrieve a challenge page. Cover tests: kagane route tests removed, the general-route guarantees they pinned kept and re-pinned — unstored and traversal-shaped addresses serve nothing (TestPublicCoverRejectsUnknownAddress), non-image content types are never echoed back (TestPublicCoverNeverEchoesNonImage + TestCoverStoreAcceptsAnySourceURL). Store content-addressing and reopen-persistence tests rewritten against PutCover/GetCover. No Site name remains in a cover code path outside the acquisition module; go test ./... green.
This commit is contained in:
+17
-15
@@ -119,7 +119,9 @@ Guidance for OpenCode (and Claude Code) working under `backend/`. See root `AGEN
|
||||
browser-fetched page and the bytes go over plain TLS. With no browser
|
||||
configured, kagane Covers are simply absent; novelfull still gets one — at
|
||||
creation and on the poll — when its page body happens to answer a plain
|
||||
request (the challenge is a live time-varying fact).
|
||||
request (the challenge is a live time-varying fact). The old kagane-only
|
||||
serving path (`/img/kagane/{id}`, template rewrite, `CoverFetcher`) is gone
|
||||
(issue #63): the one public route serves every Site.
|
||||
- **`updated_at` drives list order, so moves only on real reading progress:** server apply its timestamp when row new or `last_chapter_num` changes, else keep stored value — favouriting series or recording newly published chapter must not reorder list. `PUT` therefore returns row **as stored**, clients must adopt that response rather than own payload. See `plans/2026-07-25-bookmark-list-favorites-design.md` §4.
|
||||
- **Lifecycle buckets:** `status` on each bookmark is `reading` | `archived` |
|
||||
`finished`, orthogonal to `favorite`. Archived and finished appear only in
|
||||
@@ -156,21 +158,21 @@ Guidance for OpenCode (and Claude Code) working under `backend/`. See root `AGEN
|
||||
Reader's credential at serve time).
|
||||
`BROWSER_WS_URL` (CDP endpoint of the browser, which runs on a **separate
|
||||
machine** and is reached over the tailnet — ADR-0006, `chrome/docker-compose.yml`.
|
||||
Used by the poller for kagane and novelfull *and* by the web UI's kagane
|
||||
cover proxy; unset — the default — disables browser polling and serves 404
|
||||
for covers not already stored, leaving those sites to the userscript alone.
|
||||
Must be a tailnet IP, never a hostname: Chrome's DevTools handler 500s
|
||||
Used by the poller for kagane and novelfull page fetches and by the cover
|
||||
pipeline for kagane's image bytes (the browser is the only route that clears
|
||||
the challenge kagane serves its covers behind); unset — the default —
|
||||
disables browser polling and leaves kagane Covers blank until stored bytes
|
||||
exist. Must be a tailnet IP, never a hostname: Chrome's DevTools handler 500s
|
||||
`/json/version` for any Host that isn't an IP or `localhost`).
|
||||
- **kagane covers are proxied, not hot-linked:** kagane serves cover images
|
||||
behind the same challenge as its pages and with
|
||||
`cross-origin-resource-policy: same-origin`, so no `<img>` on the web UI's
|
||||
origin can load one — not even from a browser holding the clearance cookie
|
||||
`og:image` to `/img/kagane/{id}`. `internal/web/cover.go` reads the persistent
|
||||
`covers` table first, then fetches a miss through `latest.BrowserFetcher.Image`.
|
||||
The templates render `.CoverURL`, never `.Cover`. The id is matched against a
|
||||
UUID regex before it reaches the browser: the stored value is client-supplied,
|
||||
so an unchecked one is an SSRF primitive pointed at the deployment's own
|
||||
network.
|
||||
- **No per-Site cover path (issue #63):** every Cover — all six Sites — is
|
||||
served by the one public `GET /covers/{addr}` route from content-addressed
|
||||
bytes. There is no proxy, no per-Site rewrite, no second place that decides
|
||||
a Cover's renderable address: the wire `cover` is it. The only place a Site
|
||||
name still appears in cover code is the acquisition module, where kagane's
|
||||
image URLs are claimed by `latest.BrowserFetcher` (`browserCoverURL`) because
|
||||
they answer a plain fetch with a challenge and
|
||||
`cross-origin-resource-policy: same-origin`; every other Site's CDN answers
|
||||
plain TLS. Templates render `.Cover` — the wire value — never anything else.
|
||||
- **Web UI also owns:** session-gated `GET /install/{manga,novel}-bookmark.user.js`
|
||||
(renders the bindmounted script with the acting Reader's derived credential
|
||||
substituted in — the credential never appears in page markup, the address
|
||||
|
||||
Reference in New Issue
Block a user