oficina / memoria Public

Clone
git clone https://amadan.net/oficina/memoria

Plain git — no account needed to clone.

Download

Download this file

1package memoria
2
3import (
4 "net/http"
5 "net/http/httptest"
6 "net/url"
7 "regexp"
8 "strings"
9 "testing"
10
11 "amadan.net/oficina/home/connect"
12 "amadan.net/rastrillo/rastrillo"
13)
14
15// rastrillo #64 in Home mode: the local form's emailed link lands on the
16// confirm door, and the door's POST /auth/verify is mounted under
17// LocalGuard like the GET. Opening the link spends nothing; a cross-site
18// POST and one with no browser evidence are refused without spending it;
19// the door's own POST signs in to the local landing; the spent token is
20// ?err=expired.
21//
22// MUTATIONS (author-chosen, 2026-10-06; an independent pass is owed per
23// AGENTS.md): dropping the Home POST mount, and mounting it without
24// LocalGuard, each fail here. Reverted.
25func TestHomeLocalLinkOpensTheConfirmDoor(t *testing.T) {
26 f := newHomeRouterFixture(t)
27 homeRedirect(t, f.do("POST", "/auth/begin", "address=alice%40example.test&force=1"))
28 link := regexp.MustCompile(`https://[^\s<>\"]+/auth/verify\?[^\s<>\"]+`).FindString(f.mail.body)
29 u, err := url.Parse(link)
30 if err != nil || link == "" {
31 t.Fatal("no captured local verification link")
32 }
33 session := func(w *httptest.ResponseRecorder) bool {
34 for _, c := range w.Result().Cookies() {
35 if strings.Contains(c.Name, "session") && c.Value != "" && c.MaxAge >= 0 {
36 return true
37 }
38 }
39 return false
40 }
41 var action, token string
42 for i := 0; i < 2; i++ {
43 w := f.do("GET", u.RequestURI(), "")
44 if w.Code != http.StatusOK || session(w) {
45 t.Fatalf("GET the link = %d (session %v), want the door and nothing minted", w.Code, session(w))
46 }
47 action, token = confirmForm(t, w.Body.String())
48 }
49
50 post := func(origin, site string, extra ...*http.Cookie) *httptest.ResponseRecorder {
51 r := httptest.NewRequest("POST", homeTestOrigin+action, strings.NewReader(url.Values{"token": {token}}.Encode()))
52 r.Header.Set("Content-Type", "application/x-www-form-urlencoded")
53 if origin != "" {
54 r.Header.Set("Origin", origin)
55 }
56 if site != "" {
57 r.Header.Set("Sec-Fetch-Site", site)
58 }
59 for _, c := range extra {
60 r.AddCookie(c)
61 }
62 w := httptest.NewRecorder()
63 f.handler.ServeHTTP(w, r)
64 return w
65 }
66 for name, w := range map[string]*httptest.ResponseRecorder{
67 "cross-site": post("https://evil.example", "cross-site"),
68 "no browser proof": post("", ""),
69 } {
70 if w.Code != http.StatusForbidden || session(w) {
71 t.Errorf("%s POST = %d (session %v), want 403 and no session", name, w.Code, session(w))
72 }
73 }
74 // LocalGuard holds the POST too: a duplicated pending cookie is
75 // connect's refusal (400), before auth is asked anything.
76 v := strings.Repeat("A", 43)
77 dup := post(homeTestOrigin, "same-origin",
78 &http.Cookie{Name: "__Host-rastrillo_pending", Value: v}, &http.Cookie{Name: "__Host-rastrillo_pending", Value: v})
79 if dup.Code != http.StatusBadRequest || session(dup) {
80 t.Errorf("POST with a duplicated pending cookie = %d, want LocalGuard's 400", dup.Code)
81 }
82
83 // None of that spent it.
84 if landing := homeRedirect(t, f.openLink(t, u.RequestURI())); landing != "/auth/local/complete" {
85 t.Fatalf("the door's POST landed on %q, want /auth/local/complete", landing)
86 }
87 if replay := post(homeTestOrigin, "same-origin"); replay.Code != http.StatusSeeOther || replay.Header().Get("Location") != "/signin?err=expired" {
88 t.Errorf("a replayed token = %d %q, want 303 /signin?err=expired", replay.Code, replay.Header().Get("Location"))
89 }
90}
91
92// The confirm door's headers in Home mode, through the production stack:
93// auth's no-store, strict-origin Referrer-Policy and framing refusals
94// survive, and the CSP is connect's FormPolicy followed by auth's added
95// frame-ancestors 'none'. The standalone half is memoriatest's
96// TestTheConfirmDoorKeepsEveryPolicy; this one was missing (Astra on #27:
97// overwriting Referrer-Policy in renderConfirm only when Home is
98// configured passed both suites). Run that mutation: it fails here.
99func TestHomeConfirmDoorKeepsEveryPolicy(t *testing.T) {
100 f := newHomeRouterFixture(t)
101 h, done, err := rastrillo.Handler(rastrillo.Options{Mux: f.handler.(*http.ServeMux)})
102 if err != nil {
103 t.Fatal(err)
104 }
105 if done != nil {
106 defer done()
107 }
108 w := httptest.NewRecorder()
109 h.ServeHTTP(w, httptest.NewRequest("GET", homeTestOrigin+"/auth/verify?token=x", nil))
110 if w.Code != http.StatusOK {
111 t.Fatalf("GET /auth/verify = %d", w.Code)
112 }
113 confirmForm(t, w.Body.String())
114 for name, want := range map[string]string{
115 "Cache-Control": "no-store",
116 "Referrer-Policy": "strict-origin",
117 "X-Frame-Options": "DENY",
118 "X-Robots-Tag": "noindex, nofollow",
119 } {
120 if got := w.Header().Get(name); got != want {
121 t.Errorf("%s = %q, want %q", name, got, want)
122 }
123 }
124 got := w.Header().Values("Content-Security-Policy")
125 if want := []string{connect.FormPolicy(homeTestIssuer), "frame-ancestors 'none'"}; strings.Join(got, "\n") != strings.Join(want, "\n") {
126 t.Errorf("CSP values = %q, want %q", got, want)
127 }
128}
129