| 1 | package calls |
| 2 | |
| 3 | import ( |
| 4 | "net/http" |
| 5 | "strings" |
| 6 | |
| 7 | "amadan.net/rastrillo/rastrillo" |
| 8 | ) |
| 9 | |
| 10 | // ServeOptions completes opts with what Calls serves: its mux, its error |
| 11 | // page and its scheduled-wake function. main passes the result to |
| 12 | // rastrillo.Serve, and the tests that need the production stack (the CSP |
| 13 | // tests, the browser gate) pass it to rastrillo.Handler, so the headers |
| 14 | // they check are the ones production sends. |
| 15 | // |
| 16 | // opts.CSP is deliberately left empty: Calls serves rastrillo's default |
| 17 | // policy unchanged, the strict one since fb806a2 (style-src 'self' plus |
| 18 | // pow's honeypot hash, no 'unsafe-inline'). Nothing Calls draws needs |
| 19 | // more. Its templates, and the estilo and ui partials they call, carry |
| 20 | // no style attribute; its scripts set geometry and the speaking rim |
| 21 | // through CSSOM (element.style.setProperty, .translate, .display), which |
| 22 | // CSP does not govern; and the web client's network is all same-origin |
| 23 | // (fetch, the SSE stream), with media from getUserMedia on srcObject and |
| 24 | // TURN/STUN reached by RTCPeerConnection, neither of which connect-src |
| 25 | // or media-src covers. With Home configured, connect's middleware |
| 26 | // replaces the policy on every page in the group; homeFormPolicy keeps |
| 27 | // that one rastrillo's default too, with only the issuer added to |
| 28 | // form-action. TestProductionServesRastrillosDefaultCSP, |
| 29 | // TestHomeCSPPreservesPinnedFrameworkDirectives and the browser gate's |
| 30 | // CSP check hold this. |
| 31 | func ServeOptions(opts rastrillo.Options, mux *http.ServeMux, rt *Runtime) rastrillo.Options { |
| 32 | opts.Mux = mux |
| 33 | if rt != nil { |
| 34 | // NextDue answers the platform's scheduled-wake poll: a magic |
| 35 | // link or pending-join row can outlive an instance's |
| 36 | // hibernation, and without this the platform has no way to know |
| 37 | // it should wake the app to clear one (review C2-2). |
| 38 | opts.NextDue = rt.NextDue |
| 39 | // A panic outside the router gets the app's own error page, not |
| 40 | // rastrillo's plain "Something went wrong." (shared chrome §3.3). |
| 41 | opts.ErrorPage = rt.ErrorPage |
| 42 | } |
| 43 | return opts |
| 44 | } |
| 45 | |
| 46 | // Runtime is the parts of Runtime a test harness can have: everything |
| 47 | // but Close, which belongs to whoever built the app with App. Tests |
| 48 | // only. |
| 49 | func (h *TestHandle) Runtime() *Runtime { |
| 50 | return &Runtime{NextDue: h.a.nextDue, ErrorPage: h.a.ErrorPage} |
| 51 | } |
| 52 | |
| 53 | // frameworkCSP is rastrillo's default Content-Security-Policy at the |
| 54 | // pinned version (serve.go's defaultCSP, unexported), spelled out |
| 55 | // because homeFormPolicy must start from it. |
| 56 | // TestProductionServesRastrillosDefaultCSP fails the day a bump changes |
| 57 | // the default, so this cannot quietly keep an older policy. |
| 58 | const frameworkCSP = "default-src 'self'; " + |
| 59 | "style-src 'self' 'unsafe-hashes' 'sha256-yJxAE4rjdcckohdlnvecSporPcqS9xOaA4hJxi87LMc='; " + |
| 60 | "img-src 'self' data:; frame-ancestors 'none'; base-uri 'self'; form-action 'self'" |
| 61 | |
| 62 | // homeFormPolicy is the policy connect's middleware sends in Home mode: |
| 63 | // rastrillo's default with the issuer added to form-action, because the |
| 64 | // Home exchange posts a form there. connect's own FormPolicy predates |
| 65 | // rastrillo fb806a2 and still allows inline styles; Calls has none to |
| 66 | // allow. The one style attribute connect's pages carry (home_link.html's |
| 67 | // overflow-wrap on the linked address) is in calls-shell.css instead. |
| 68 | func homeFormPolicy(issuer string) string { |
| 69 | return strings.Replace(frameworkCSP, "form-action 'self'", "form-action 'self' "+issuer, 1) |
| 70 | } |
| 71 | |