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:
+35
-1
@@ -36,7 +36,7 @@ func sessionCookie(t *testing.T, cfg Config) *http.Cookie {
|
||||
t.Helper()
|
||||
return &http.Cookie{
|
||||
Name: sessionCookieName,
|
||||
Value: signSession(sessionKey(cfg.Token), time.Now().Add(time.Hour).UnixMilli()),
|
||||
Value: signSession(sessionKey(cfg.Token, cfg.WebPassword), time.Now().Add(time.Hour).UnixMilli()),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -352,6 +352,40 @@ func TestChapterOverrideMovesUpdatedAt(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestChapterOverrideNoOpPreservesURLAndUpdatedAt(t *testing.T) {
|
||||
cfg := webConfig()
|
||||
srv, store := newWebTestServer(t, cfg)
|
||||
before := seed(t, store, Bookmark{
|
||||
Key: "asura:solo", Site: "asura", SeriesID: "solo",
|
||||
Title: "Solo Leveling", LastChapter: "45", LastChapterNum: 45,
|
||||
LastChapterURL: "https://example.test/ch/45", SeriesURL: "https://example.test/solo",
|
||||
UpdatedAt: 1_000_000,
|
||||
})
|
||||
|
||||
// The chapter form is pre-filled with the current value, so tapping Save
|
||||
// without editing resubmits the unchanged number. That must be a no-op:
|
||||
// it must not silently clear last_chapter_url or move updated_at.
|
||||
rr := httptest.NewRecorder()
|
||||
srv.ServeHTTP(rr, uiRequest(t, cfg, http.MethodPost,
|
||||
"/ui/bookmarks/asura:solo/chapter", url.Values{"chapter": {"45"}}))
|
||||
if rr.Code != http.StatusOK {
|
||||
t.Fatalf("chapter no-op status = %d, want 200", rr.Code)
|
||||
}
|
||||
|
||||
after, ok, err := store.Get("asura:solo")
|
||||
if err != nil || !ok {
|
||||
t.Fatalf("Get after no-op override: %v ok=%v", err, ok)
|
||||
}
|
||||
if after.LastChapterURL != before.LastChapterURL {
|
||||
t.Fatalf("LastChapterURL = %q, want preserved %q on a no-op save",
|
||||
after.LastChapterURL, before.LastChapterURL)
|
||||
}
|
||||
if after.UpdatedAt != before.UpdatedAt {
|
||||
t.Fatalf("UpdatedAt = %d, want unchanged %d on a no-op save",
|
||||
after.UpdatedAt, before.UpdatedAt)
|
||||
}
|
||||
}
|
||||
|
||||
func TestChapterOverrideRejectsBadInput(t *testing.T) {
|
||||
cfg := webConfig()
|
||||
srv, store := newWebTestServer(t, cfg)
|
||||
|
||||
Reference in New Issue
Block a user