package calls import ( "amadan.net/oficina/home/connect" "amadan.net/rastrillo/rastrillo/assertion" assertioncrypto "amadan.net/rastrillo/rastrillo/crypto" "amadan.net/rastrillo/rastrillo/db" "bytes" "context" "crypto/sha256" "encoding/base64" "encoding/hex" "encoding/json" "html" "io" "log/slog" "net/http" "net/http/httptest" "net/url" "path/filepath" "regexp" "strings" "testing" "time" ) const homeTestOrigin = "https://meet.example.test" const homeTestIssuer = "https://id.example.test" type homeTestMail struct{ body string } func (m *homeTestMail) Send(_ context.Context, _, _, body string) error { m.body = body; return nil } type homeRouterFixture struct { app *app handler http.Handler key *assertioncrypto.Keypair mail *homeTestMail cookies map[string]*http.Cookie } func newHomeRouterFixture(t *testing.T) *homeRouterFixture { t.Helper() logger := slog.New(slog.NewTextHandler(io.Discard, nil)) d, err := db.Open(filepath.Join(t.TempDir(), "calls.db"), logger) if err != nil { t.Fatal(err) } t.Cleanup(func() { d.Close() }) key, err := assertioncrypto.Generate() if err != nil { t.Fatal(err) } return newHomeRouterFixtureWith(t, d, logger, key, &connect.Credentials{Issuer: homeTestIssuer, ConsumerID: "calls-test", KeysBase64: homeKeysBase64(key), DEKBase64: base64.StdEncoding.EncodeToString(bytes.Repeat([]byte{7}, 32))}) } // homeKeysBase64 is the public-key manifest Home publishes for key. func homeKeysBase64(key *assertioncrypto.Keypair) string { sum := sha256.Sum256(key.SignPub()) id := hex.EncodeToString(sum[:]) raw, _ := json.Marshal(map[string]string{id: base64.StdEncoding.EncodeToString(key.SignPub())}) return base64.StdEncoding.EncodeToString(raw) } // newHomeRouterFixtureWith builds the app with Home configured by creds, // whose assertions key signs. func newHomeRouterFixtureWith(t *testing.T, d *db.DB, logger *slog.Logger, key *assertioncrypto.Keypair, creds *connect.Credentials) *homeRouterFixture { t.Helper() mail := &homeTestMail{} mux, a, err := buildForTest(t, d, Config{Origin: homeTestOrigin, InstanceKey: "test-only-instance", DEK: base64.StdEncoding.EncodeToString(bytes.Repeat([]byte{3}, 32)), AllowedAddresses: []string{"alice@example.test"}, Mailer: mail, Logger: logger, Home: creds}) if err != nil { t.Fatal(err) } return &homeRouterFixture{a, mux, key, mail, map[string]*http.Cookie{}} } // mailBody is the last sign-in mail, once the queue has sent it // (links go out after the response: signin_mail.go). func (f *homeRouterFixture) mailBody() string { f.app.signinMail.wait() return f.mail.body } func homeKeyID(key *assertioncrypto.Keypair) string { sum := sha256.Sum256(key.SignPub()) return hex.EncodeToString(sum[:]) } func (f *homeRouterFixture) do(method, path, body string) *httptest.ResponseRecorder { return f.doWith(method, path, body, nil) } // doWith is do with extra request headers, set after do's own. func (f *homeRouterFixture) doWith(method, path, body string, extra http.Header) *httptest.ResponseRecorder { r := httptest.NewRequest(method, homeTestOrigin+path, strings.NewReader(body)) for _, c := range f.cookies { r.AddCookie(c) } if method == "POST" { r.Header.Set("Content-Type", "application/x-www-form-urlencoded") r.Header.Set("Origin", homeTestOrigin) if path == "/auth/assertion" { r.Header.Set("Origin", homeTestIssuer) } } for name, values := range extra { r.Header[name] = values } w := httptest.NewRecorder() f.handler.ServeHTTP(w, r) for _, c := range w.Result().Cookies() { if c.MaxAge < 0 { delete(f.cookies, c.Name) } else { f.cookies[c.Name] = c } } return w } // confirmForm is the confirm door's one form, as a browser reads it. var confirmForm = regexp.MustCompile(`