oficina / home Public

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

Plain git — no account needed to clone.

Download

Download this file

1package connect
2
3import (
4 "amadan.net/oficina/home/handoff/seal"
5 "bytes"
6 "net/http"
7 "net/http/httptest"
8 "testing"
9 "time"
10)
11
12const navigationOrigin = "https://slides.example.com"
13
14func navigationFixture(t *testing.T) (*Session, *localLinkNavigation) {
15 t.Helper()
16 s := &Session{Subject: "existing@example.com", Method: "magiclink", At: time.Now()}
17 sealer, err := seal.New(bytes.Repeat([]byte{19}, 32), "oficina/slides")
18 if err != nil {
19 t.Fatal(err)
20 }
21 n := newLocalLinkNavigation(sealer, RoutesConfig{Namespace: "oficina/slides", Origin: navigationOrigin, SessionCookie: "__Host-rastrillo_session", ReadSession: func(r *http.Request) (*Session, error) {
22 c, err := r.Cookie("__Host-rastrillo_session")
23 if err != nil || c.Value != "verified" {
24 return nil, nil
25 }
26 return s, nil
27 }, Render: func(w http.ResponseWriter, r *http.Request, state, label string, status int) { w.WriteHeader(status) }})
28 if n == nil {
29 t.Fatal("navigation rejected")
30 }
31 return s, n
32}
33func TestNavigationRequiresActualFreshLocalSession(t *testing.T) {
34 for _, tc := range []struct {
35 method string
36 age time.Duration
37 auth bool
38 want string
39 }{{"magiclink", 0, false, "/account/home"}, {"magiclink", 6 * time.Minute, false, "/"}, {"keymail", 0, false, "/"}, {"keymail", 0, true, "/account/home"}, {HomeMethod, 0, true, "/"}} {
40 t.Run(tc.method+tc.age.String()+tc.want, func(t *testing.T) {
41 s, n := navigationFixture(t)
42 now := time.Now()
43 n.now = func() time.Time { return now }
44 s.Method = tc.method
45 s.At = now.Add(-tc.age)
46 if tc.auth {
47 s.AuthTime = s.At
48 }
49 w := httptest.NewRecorder()
50 n.Begin(w, httptest.NewRequest("GET", navigationOrigin+"/account/home", nil))
51 if w.Code != 303 || w.Header().Get("Location") != "/signin/local" {
52 t.Fatal(w.Code, w.Header())
53 }
54 r := httptest.NewRequest("GET", navigationOrigin+localLinkCompletePath, nil)
55 for _, c := range w.Result().Cookies() {
56 if c.Name == localLinkNavigationCookie && c.MaxAge > 0 {
57 r.AddCookie(c)
58 }
59 }
60 r.AddCookie(&http.Cookie{Name: n.sessionCookie, Value: "verified"})
61 done := httptest.NewRecorder()
62 n.Complete(done, r)
63 if done.Header().Get("Location") != tc.want {
64 t.Fatal(done.Header())
65 }
66 r.AddCookie(&http.Cookie{Name: n.sessionCookie, Value: "verified"})
67 duplicate := httptest.NewRecorder()
68 n.Complete(duplicate, r)
69 if duplicate.Header().Get("Location") != "/" {
70 t.Fatal("duplicate session accepted")
71 }
72 })
73 }
74}
75func TestNavigationPurposeIsolationAndFixedAction(t *testing.T) {
76 _, n := navigationFixture(t)
77 w := httptest.NewRecorder()
78 n.Begin(w, httptest.NewRequest("GET", navigationOrigin+"/account/home", nil))
79 r := httptest.NewRequest("GET", navigationOrigin+localLinkCompletePath, nil)
80 for _, c := range w.Result().Cookies() {
81 if c.MaxAge > 0 {
82 r.AddCookie(c)
83 }
84 }
85 if !n.marker(r, n.now().Unix()) {
86 t.Fatal("valid marker refused")
87 }
88 other := *n
89 other.namespace = "oficina/calls"
90 if other.marker(r, n.now().Unix()) {
91 t.Fatal("cross-app marker accepted")
92 }
93 for _, target := range []string{navigationOrigin + "/account/home?return_to=https://bad.test", "https://bad.test/account/home"} {
94 bad := httptest.NewRecorder()
95 n.Begin(bad, httptest.NewRequest("GET", target, nil))
96 for _, c := range bad.Result().Cookies() {
97 if c.MaxAge > 0 {
98 t.Fatal("wrong host/query minted navigation")
99 }
100 }
101 }
102}
103
104func TestNavigationLocalLandingPathIsSafeAndDoesNotOverrideLink(t *testing.T) {
105 _, n := navigationFixture(t)
106 config := RoutesConfig{Namespace: n.namespace, Origin: n.origin, SessionCookie: n.sessionCookie, ReadSession: n.readSession, Render: func(w http.ResponseWriter, r *http.Request, state, label string, status int) { w.WriteHeader(status) }}
107 for _, target := range []string{"https://other.example/", "//other.example/", "/\\other.example/"} {
108 config.LocalLandingPath = target
109 if newLocalLinkNavigation(n.sealer, config) != nil {
110 t.Fatal("unsafe local landing accepted", target)
111 }
112 }
113 config.LocalLandingPath = "/continue"
114 land := newLocalLinkNavigation(n.sealer, config)
115 if land == nil {
116 t.Fatal("safe local landing refused")
117 }
118 r := navigationRequest(localLinkCompletePath)
119 w := httptest.NewRecorder()
120 land.Complete(w, r)
121 if w.Header().Get("Location") != "/continue" {
122 t.Fatal("local landing ignored")
123 }
124 marker := navigationMarker(t, land)
125 r.AddCookie(marker)
126 r.AddCookie(&http.Cookie{Name: land.sessionCookie, Value: "verified"})
127 w = httptest.NewRecorder()
128 land.Complete(w, r)
129 if w.Header().Get("Location") != "/account/home" {
130 t.Fatal("landing replaced explicit link navigation")
131 }
132}
133