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:
+11
-5
@@ -22,11 +22,13 @@ const (
|
||||
sessionKeyPurpose = "mangabm-web-session-v1"
|
||||
)
|
||||
|
||||
// sessionKey derives the cookie-signing key from the API token. Sessions are
|
||||
// stateless — there is no session table — so rotating API_TOKEN invalidates
|
||||
// every outstanding cookie at once.
|
||||
func sessionKey(apiToken string) []byte {
|
||||
sum := sha256.Sum256([]byte(apiToken + sessionKeyPurpose))
|
||||
// sessionKey derives the cookie-signing key from both secrets. Sessions are
|
||||
// stateless — there is no session table — so rotating either API_TOKEN or
|
||||
// WEB_PASSWORD invalidates every outstanding cookie at once. The \x00
|
||||
// separator prevents the concatenation ambiguity a bare apiToken+webPassword
|
||||
// would have (e.g. "ab"+"c" colliding with "a"+"bc").
|
||||
func sessionKey(apiToken, webPassword string) []byte {
|
||||
sum := sha256.Sum256([]byte(apiToken + "\x00" + webPassword + sessionKeyPurpose))
|
||||
return sum[:]
|
||||
}
|
||||
|
||||
@@ -166,6 +168,10 @@ func (l *loginLimiter) reset(ip string) {
|
||||
// The caller must hold l.mu.
|
||||
func (l *loginLimiter) pruneLocked(ip string, now time.Time) []time.Time {
|
||||
cutoff := now.Add(-loginWindow)
|
||||
// In-place filter: kept reuses the backing array of the slice being
|
||||
// ranged over. The range expression captures the slice header once at
|
||||
// the start, so the append cursor (kept) can never outrun the read
|
||||
// cursor (the range index) — safe to alias.
|
||||
kept := l.failures[ip][:0]
|
||||
for _, at := range l.failures[ip] {
|
||||
if at.After(cutoff) {
|
||||
|
||||
Reference in New Issue
Block a user