fix(backend): bind session key to both secrets and guard no-op chapter saves
Final-review fix wave over the web UI branch. - sessionKey now derives from API_TOKEN and WEB_PASSWORD with a \x00 separator, so rotating the password logs every browser out too. - uiChapter only clears last_chapter_url when the number actually changes. The form is pre-filled, so a bare tap of Save resubmits the same value; that used to destroy the chapter URL silently while updated_at stayed put, degrading Continue to the series index page. - MANGA_WEB_HOST is now required by the prod override rather than falling back to manga.example.com, matching MANGA_API_HOST. - Comment fixes: static cache rationale, pruneLocked aliasing invariant, and the stale "3 routes" line in CLAUDE.md. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+14
-8
@@ -55,7 +55,7 @@ func newWebHandler(store *Store, cfg Config) (*webHandler, error) {
|
||||
return &webHandler{
|
||||
store: store,
|
||||
tmpl: tmpl,
|
||||
key: sessionKey(cfg.Token),
|
||||
key: sessionKey(cfg.Token, cfg.WebPassword),
|
||||
password: cfg.WebPassword,
|
||||
limiter: newLoginLimiter(),
|
||||
}, nil
|
||||
@@ -73,9 +73,10 @@ func (h *webHandler) register(mux *http.ServeMux) {
|
||||
mux.HandleFunc("DELETE /ui/bookmarks/{key}", h.requireSession(h.uiDelete))
|
||||
}
|
||||
|
||||
// staticHandler serves the embedded assets. The vendored htmx build and the
|
||||
// stylesheet change only on deploy, so a long max-age is safe; a redeploy
|
||||
// changes the binary and the browser revalidates on its own schedule.
|
||||
// staticHandler serves the embedded assets. An hour, not longer: assets are
|
||||
// not fingerprinted, and embed.FS reports a zero ModTime, so http.FileServer
|
||||
// emits no Last-Modified or ETag and a client has no way to revalidate a
|
||||
// cached copy after a deploy short of waiting out max-age.
|
||||
func staticHandler() http.Handler {
|
||||
sub, err := fs.Sub(staticFS, "static")
|
||||
if err != nil {
|
||||
@@ -253,9 +254,12 @@ func (h *webHandler) uiFavorite(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// uiChapter forces the read chapter to a value the user typed.
|
||||
//
|
||||
// It clears last_chapter_url: that URL points at the chapter actually read, and
|
||||
// once the number is forced elsewhere it would send the reader backwards.
|
||||
// ContinueURL then falls back to the series page, which is always right.
|
||||
// When that value actually changes the number, it also clears last_chapter_url:
|
||||
// that URL points at the chapter actually read, and once the number is forced
|
||||
// elsewhere it would send the reader backwards. ContinueURL then falls back to
|
||||
// the series page, which is always right. Resubmitting the same number — the
|
||||
// form is pre-filled, so a bare tap of Save is an easy accidental submit —
|
||||
// leaves last_chapter_url untouched instead of destroying it for no reason.
|
||||
func (h *webHandler) uiChapter(w http.ResponseWriter, r *http.Request) {
|
||||
b, ok := h.loadForMutation(w, r)
|
||||
if !ok {
|
||||
@@ -272,9 +276,11 @@ func (h *webHandler) uiChapter(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
if num != b.LastChapterNum {
|
||||
b.LastChapterURL = ""
|
||||
}
|
||||
b.LastChapter = raw
|
||||
b.LastChapterNum = num
|
||||
b.LastChapterURL = ""
|
||||
b.UpdatedAt = time.Now().UnixMilli()
|
||||
h.saveAndRenderCard(w, b)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user