v0.245.0 — R-543: the household is asked for the recovery code, the page says "szunetel" until then
gates / gates (push) Successful in 14s
gates / gates (push) Successful in 14s
Off-site backup is ON by default and does not RUN until the household creates its recovery code. The pause is the zero-knowledge escrow design and is untouched here; what was missing is that nothing ASKED, while the app-backup page promised the very copy that had never run. - a reminder bar on every authenticated page while the off-site tier is configured and its escrow is not complete, linking /backup/escrow. It is the R-241 bar, second instance: same session-cookie dismissal, back next visit, gone for good when escrowed. No second banner system. It hangs off executeTemplate, the single render choke point, so it cannot reach only the pages someone remembered. - the tier-1 file sentence renders by tier3State's own vocabulary instead of the app's shape: active -> "vedi", escrow_pending -> "vedene ... szunetel" + the route, no copy at all -> says so and names both ways out. - both fixes red-proofed: the bar test fails on BOTH pages with the hook removed; the sentence test quotes the exact v0.244.0 promise when the state is ignored. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
This commit is contained in:
@@ -0,0 +1,91 @@
|
||||
package web
|
||||
|
||||
import "net/http"
|
||||
|
||||
// The escrow reminder bar (R-543) — the household is ASKED for its recovery code.
|
||||
//
|
||||
// Off-site backup is ON by default (hub v0.116.0), and it does not RUN until the household has
|
||||
// created its recovery code. That pause is a DESIGN, not a defect: the escrow is zero-knowledge, the
|
||||
// household's code is the only key, and a run started without one would produce a copy nobody could
|
||||
// ever open. Nothing here touches that mechanism.
|
||||
//
|
||||
// What was missing is that nothing ASKED. A fresh box sat at „Kulcsletétre vár" indefinitely while
|
||||
// the backup page told the household their files were protected — measured on a fresh box
|
||||
// 2026-09-16. A promise plus a pause nobody mentions is the same class of untruth as R-537 and R-538.
|
||||
//
|
||||
// This is the R-241 reminder bar, SECOND INSTANCE, on purpose: same session-cookie dismissal, same
|
||||
// layout block shape, same "back at the next visit" behaviour. No second banner system was built.
|
||||
const escrowBannerCookie = "felhom_escrow_banner"
|
||||
|
||||
// escrowPaused reads the same two facts tier3State reads (backup_page_state.go): the off-site tier is
|
||||
// configured, and its escrow is not complete. Deliberately one predicate — a second copy would let
|
||||
// the bar and the app rows tell the household two different stories about the same box.
|
||||
func (s *Server) escrowPaused() bool {
|
||||
if s.backupMgr == nil || !s.backupMgr.OffboxConfigured() || s.settings == nil {
|
||||
return false
|
||||
}
|
||||
t := s.settings.GetOffboxTarget()
|
||||
if t == nil {
|
||||
return false
|
||||
}
|
||||
// The vocabulary is "" | "pending" | "escrowed" (agentapi). Anything that is not the finished
|
||||
// state is a paused state — fail LOUD, so an unknown value reminds rather than goes quiet.
|
||||
return t.EscrowState != "escrowed"
|
||||
}
|
||||
|
||||
// hasAdminSession — the household is logged in right now.
|
||||
//
|
||||
// It mirrors RequireAuth's own check (auth.go), including the legacy-open box where no password is
|
||||
// configured and every page is served. It exists because the bar hangs off executeTemplate, the
|
||||
// render choke point for EVERY page: the login and claim pages bypass that function entirely, and
|
||||
// this check is what additionally keeps a household reminder off the public guest share page, which
|
||||
// carries a capability token and no admin session.
|
||||
func (s *Server) hasAdminSession(r *http.Request) bool {
|
||||
if !s.authEnabled() {
|
||||
return true
|
||||
}
|
||||
c, err := r.Cookie(sessionCookieName)
|
||||
return err == nil && s.isValidSession(c.Value)
|
||||
}
|
||||
|
||||
// escrowBannerVisible — logged in, the tier is paused, and they have not clicked it away this visit.
|
||||
func (s *Server) escrowBannerVisible(r *http.Request) bool {
|
||||
if r == nil || !s.hasAdminSession(r) || !s.escrowPaused() {
|
||||
return false
|
||||
}
|
||||
if c, err := r.Cookie(escrowBannerCookie); err == nil && c.Value == "1" {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// addEscrowBanner is called from executeTemplate for every authenticated page.
|
||||
func (s *Server) addEscrowBanner(data map[string]interface{}, r *http.Request) {
|
||||
if data == nil || !s.escrowBannerVisible(r) {
|
||||
return
|
||||
}
|
||||
data["EscrowBanner"] = true
|
||||
data["EscrowBannerBack"] = r.URL.Path
|
||||
if data["CSRFField"] == nil {
|
||||
data["CSRFField"] = s.csrfField(r)
|
||||
}
|
||||
// STATE, never customer data: which page, and the reason the bar is up. DEBUG because this fires
|
||||
// on every page render and INFO is the operator's state-change level.
|
||||
if s.isDebug() {
|
||||
s.logger.Printf("[DEBUG] [web] escrow reminder: rendered on %s (offsite configured, escrow not complete)", r.URL.Path)
|
||||
}
|
||||
}
|
||||
|
||||
// escrowBannerDismissHandler records „Most nem" (POST /backup/escrow/banner/dismiss) — a browser
|
||||
// SESSION cookie and nothing durable, exactly like R-241. The off-site tier is still paused whether
|
||||
// or not anyone clicked, so the bar is back at the next visit and gone for good only when the escrow
|
||||
// state becomes "escrowed".
|
||||
func (s *Server) escrowBannerDismissHandler(w http.ResponseWriter, r *http.Request) {
|
||||
http.SetCookie(w, &http.Cookie{
|
||||
Name: escrowBannerCookie, Value: "1", Path: "/",
|
||||
HttpOnly: true, SameSite: http.SameSiteLaxMode, Secure: r.TLS != nil,
|
||||
// NO MaxAge and NO Expires — a session cookie, deliberately.
|
||||
})
|
||||
s.logger.Printf("[INFO] [web] escrow reminder: dismissed for this browser session; the off-site tier stays paused until the recovery code exists")
|
||||
http.Redirect(w, r, redirectBackTo(r, "/launcher"), http.StatusFound)
|
||||
}
|
||||
Reference in New Issue
Block a user