oficina / memoria Public

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

Plain git — no account needed to clone.

Download

Download this file

1package memoriatest
2
3import (
4 "crypto/sha256"
5 "encoding/base64"
6 "encoding/json"
7 "net/http"
8 "net/http/httptest"
9 "regexp"
10 "strings"
11 "testing"
12)
13
14// nativeApprovalPage starts a native sign-in on h and opens its approval
15// page as c, the signed-in browser, returning the response.
16func nativeApprovalPage(t *testing.T, h http.Handler, c *client) *httptest.ResponseRecorder {
17 t.Helper()
18 sum := sha256.Sum256([]byte(strings.Repeat("v", 43)))
19 req := httptest.NewRequest(http.MethodPost, testOrigin+"/native/request",
20 strings.NewReader(`{"challenge":"`+base64.RawURLEncoding.EncodeToString(sum[:])+`"}`))
21 req.Header.Set("Content-Type", "application/json")
22 req.Header.Set("Origin", testOrigin)
23 rec := httptest.NewRecorder()
24 h.ServeHTTP(rec, req)
25 var out struct{ ID string }
26 if rec.Code != http.StatusOK || json.Unmarshal(rec.Body.Bytes(), &out) != nil || out.ID == "" {
27 t.Fatalf("POST /native/request = %d %q", rec.Code, rec.Body.String())
28 }
29 page := c.get(testOrigin + "/native/authorize?id=" + out.ID)
30 if page.Code != http.StatusOK || !strings.Contains(page.Body.String(), "Sign in to Memoria app") {
31 t.Fatalf("GET /native/authorize = %d, not the approval page:\n%s", page.Code, page.Body.String())
32 }
33 return page
34}
35
36var styleElement = regexp.MustCompile(`(?s)<style>(.*?)</style>`)
37
38// Astra on #27, P2: the native kit's approval page (/native/authorize) is
39// styled by one inline <style> element, and rastrillo fb806a2's strict
40// default refused it, so the page lost its card, spacing and button. The
41// fix allows that stylesheet by its hash, on that route only.
42//
43// Read through the production stack: the served stylesheet's own hash
44// must be in the route's style-src (so a native bump that changes the CSS
45// fails here, not on a phone), and with that one source taken out the
46// policy is rastrillo's default exactly, so nothing else is relaxed. No
47// other page carries the hash.
48//
49// Fails on 651f370 (no hash on the route). MUTATIONS (author-chosen,
50// 2026-10-06): dropping the wrapper from the GET route; 'unsafe-inline'
51// in place of the hash; the wrapper on every route. Each fails here.
52// Reverted.
53func TestNativeApprovalStyleIsTheHashedOne(t *testing.T) {
54 c, h := signedInServedClient(t)
55 page := nativeApprovalPage(t, h, c)
56 m := styleElement.FindStringSubmatch(page.Body.String())
57 if m == nil {
58 t.Fatal("the approval page has no <style> element; if native/server moved its CSS out, drop the hash (memoria native.go)")
59 }
60 sum := sha256.Sum256([]byte(m[1]))
61 source := "'sha256-" + base64.StdEncoding.EncodeToString(sum[:]) + "'"
62
63 policies := page.Header().Values("Content-Security-Policy")
64 if len(policies) != 1 {
65 t.Fatalf("CSP values = %q, want one", policies)
66 }
67 if !strings.Contains(policies[0], "style-src ") || !strings.Contains(policies[0], " "+source) {
68 t.Fatalf("the approval page's CSP %q does not admit its own stylesheet %s", policies[0], source)
69 }
70 if strings.Contains(policies[0], "'unsafe-inline'") {
71 t.Errorf("the approval page allows every inline style: %q", policies[0])
72 }
73 if got, want := strings.Replace(policies[0], " "+source, "", 1), defaultCSP(t); got != want {
74 t.Errorf("with the native hash taken out, the policy is %q, want rastrillo's default %q", got, want)
75 }
76
77 for _, path := range []string{"/", "/signin", "/auth/verify?token=x"} {
78 if got := strings.Join(c.get(testOrigin+path).Header().Values("Content-Security-Policy"), "\n"); strings.Contains(got, source) {
79 t.Errorf("%s carries the native approval page's hash: %q", path, got)
80 }
81 }
82}
83
84// signedInServedClient is a browser signed in through the real magic link
85// on servedApp, the production stack.
86func signedInServedClient(t *testing.T) (*client, http.Handler) {
87 t.Helper()
88 h, mailer := servedApp(t, tempDB(t), []string{chromeOwner})
89 c := newClient(t, h)
90 if rec := c.postSameOrigin(testOrigin+"/auth/begin", map[string][]string{"address": {chromeOwner}}); rec.Code != http.StatusSeeOther {
91 t.Fatalf("POST /auth/begin = %d", rec.Code)
92 }
93 link := regexp.MustCompile(`/auth/verify\?[^\s<>"]+`).FindString(mailer.text())
94 if link == "" {
95 t.Fatalf("no magic link: %q", mailer.text())
96 }
97 if rec := c.openLink(testOrigin + link); rec.Code != http.StatusSeeOther {
98 t.Fatalf("redeem = %d", rec.Code)
99 }
100 return c, h
101}
102