Files
mesh-catalog/modules/mesh-mail-web/cmd/mail-page-dev/main.go
T
jochen d14ef647d6 issue 1859: mark the page after an act by a one-shot cookie, not its address
Review round 2 F3 (ADR 0517 §3, 1858/AC5): acted=1 stayed in the address, so a reload, a restored tab or a
bookmark of it recorded no page opened or opened. An act's 303 (and a mode chosen) now sets mail_acted for
10 seconds, HttpOnly and SameSite=Strict, Secure but on 127.0.0.1; the next list or pane it reaches records
nothing and clears it; the address carries no marker. The local run keeps the browser's other cookies.
2026-10-22 20:29:17 +02:00

139 lines
4.7 KiB
Go

// mail-page-dev: the mail page, run locally to click through (issue 1859.5). It serves mesh-mail-web's own page
// handlers (internal/web, NewServer) on 127.0.0.1 only, over invented mail in two accounts and an in-memory
// mail reading; nothing reaches the mesh: no bus, no database, no Keycloak, no mail server, and nothing is
// written to disk. Sign-in is a stand-in that is always signed in, so every form works. It is not part of the
// module's build (module.json builds cmd/mesh-mail-web alone).
//
// go run ./cmd/mail-page-dev [-addr 127.0.0.1:8463] [-now 2026-10-22T09:00:00+02:00]
package main
import (
"errors"
"flag"
"fmt"
"net"
"net/http"
"os"
"time"
"mesh-mail-web/internal/web"
)
// DefaultAddr is where the local run listens unless told another 127.0.0.1 address.
const DefaultAddr = "127.0.0.1:8463"
// localRun is a running local page.
type localRun struct {
URL string
server *http.Server
world *world
}
func (l *localRun) Close() error { return l.server.Close() }
// newBundle is the page's Bundle over the world's stand-ins: every seat the page reaches is given, so nothing
// ever falls back to the bus.
func newBundle(w *world) *web.Bundle {
r := w.read
return &web.Bundle{
Accounts: w.accounts,
Search: w.search,
Read: w.readMail,
Send: w.send,
SendStatus: w.sendStatus,
AccountBucket: w.bucket,
MailEntries: r.verb(r.entriesVerb),
MailGroups: r.verb(r.groupsVerb),
MailStatus: r.verb(r.statusVerb),
MailCorrect: r.verb(r.correctVerb),
MailLessons: r.verb(r.lessonsVerb),
MailUndo: r.verb(r.undoVerb),
MailList: r.verb(r.listVerb),
MailHandle: r.verb(r.act("handle")),
MailUnhandle: r.verb(r.act("unhandle")),
MailRemind: r.verb(r.act("remind")),
MailRecord: r.verb(r.recordVerb),
ModesSetting: func() (map[string][]string, error) {
return map[string][]string{"work": {"work"}, "personal": {"personal"}}, nil
},
Now: w.now,
}
}
// start serves the page at addr, which must be a 127.0.0.1 address; now fixes "today" when not zero.
func start(addr string, now time.Time) (*localRun, error) {
host, _, err := net.SplitHostPort(addr)
if err != nil {
return nil, fmt.Errorf("-addr %q is not host:port: %v", addr, err)
}
if host != "127.0.0.1" {
return nil, errors.New("-addr must be a 127.0.0.1 address: the local run serves this machine alone")
}
clock := time.Now
begin := time.Now()
if !now.IsZero() {
offset := now.Sub(begin)
clock = func() time.Time { return time.Now().Add(offset) }
begin = now
}
w := newWorld(begin, clock)
// The stand-in sign-in: one session, made now, put on every request in place of whatever cookie the
// browser carries — so every page and every form (their token is this session's) works, and no request
// ever goes to sign in.
sessions := web.NewSessions(clock)
id, err := sessions.Create("operator (the local run's stand-in)")
if err != nil {
return nil, err
}
page := web.NewServer(host, &web.Auth{Sessions: sessions, Pending: web.NewPendingAuths(clock)}, newBundle(w))
signedIn := http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
// Every cookie the browser carries but the session's own (the act's, web.ActedCookie, among them).
kept := req.Cookies()
req.Header.Del("Cookie")
for _, c := range kept {
if c.Name != web.SessionCookie {
req.AddCookie(c)
}
}
req.AddCookie(&http.Cookie{Name: web.SessionCookie, Value: id})
page.ServeHTTP(rw, req)
})
ln, err := net.Listen("tcp", addr)
if err != nil {
return nil, err
}
srv := &http.Server{Handler: signedIn, ReadHeaderTimeout: 10 * time.Second}
go func() { _ = srv.Serve(ln) }()
return &localRun{URL: "http://" + ln.Addr().String() + "/", server: srv, world: w}, nil
}
func main() {
addr := flag.String("addr", DefaultAddr, "where to listen: a 127.0.0.1 address only")
nowFlag := flag.String("now", "", "fix today's time (RFC 3339, such as 2026-10-22T09:00:00+02:00), to show the list on that day")
flag.Parse()
var now time.Time
if *nowFlag != "" {
t, err := time.Parse(time.RFC3339, *nowFlag)
if err != nil {
fmt.Fprintf(os.Stderr, "mail-page-dev: -now %q is not RFC 3339: %v\n", *nowFlag, err)
os.Exit(2)
}
now = t
}
run, err := start(*addr, now)
if err != nil {
fmt.Fprintf(os.Stderr, "mail-page-dev: %v\n", err)
os.Exit(1)
}
fmt.Printf("mail-page-dev: the mail page with invented mail, at %s\n", run.URL)
fmt.Println("mail-page-dev: it serves 127.0.0.1 alone; sign-in is a stand-in, always signed in")
fmt.Println("mail-page-dev: nothing reaches the mesh: no bus, no database, no Keycloak, no mail; nothing is kept after it stops")
if !now.IsZero() {
fmt.Printf("mail-page-dev: today is fixed at %s\n", now.Format(time.RFC3339))
}
fmt.Println("mail-page-dev: Ctrl-C stops it")
select {}
}